Krok Odds API
134 REST endpoints covering live odds from 140+ Australian bookmakers, the full AU racing stack, built-in arb/+EV/SGM scanners, AI tips and historical archives. JSON in, JSON out, no SDK required.
curl -H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/opportunities/positive-ev?sport=aussierules_afl&limit=10"Getting Started
Authentication
Every request (except /status) needs an API key. Pass it as the X-API-Key header, or as a ?api_key= / ?apikey= query parameter if you can't set custom headers. Get a free key at /api-access – no credit card.
Base URL & format
REST over HTTPS, JSON in and out. https://krokodds.com.au/api/v1 is the root for every endpoint below. No SDK needed – works from any language with an HTTP client. CORS is open (Access-Control-Allow-Origin: *) so you can call it directly from the browser.
Response envelope
Every endpoint returns the same shape. meta always carries count, tier and timestamp, plus endpoint-specific fields. A query that matches nothing returns data: [] with HTTP 200 (sometimes 204) – and the credit is refunded, so empty results are free.
{
"success": true,
"data": [ /* array of results, or a single object for a few endpoints */ ],
"meta": {
"count": 12,
"tier": "free",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": { "limit": 100, "remaining": 47, "reset": "2026-10-01T00:00:00.000Z" }
}
}Errors
Errors use the same envelope with success: false and an error string. A handful of legacy endpoints omit success on error responses – always check the HTTP status code, don't rely solely on the body shape.
| Status | Meaning |
|---|---|
| 400 | Bad request – a query parameter failed validation |
| 401 | Missing or invalid API key |
| 402 | Monthly credit limit exceeded – top up or enable overage |
| 403 | Your tier doesn’t include this feature (upgrade to the API plan) |
| 404 | Resource not found (e.g. unknown event/horse/date) |
| 429 | Rate limit exceeded – back off and retry with the Retry-After header |
| 500 | Internal error – safe to retry |
Credit System
Requests are metered in credits, not flat request counts – a heavy archive pull costs more than a simple lookup. Every response carries X-Credits-Cost and X-Credits-Remaining headers so you can meter your own usage without a dashboard round-trip. A request that returns zero rows is refunded in full.
Free tier
- 50 credits / month
- 5 requests / minute
- 100-row cap per request (most endpoints)
- 300s minimum cache – good for evaluating the API, not production traffic
- No access to
historicalorbulk_exportendpoints
API plan – A$49/mo
- 50,000 credits / month included
- 500 requests / minute
- Up to 500–5,000 rows per request depending on endpoint
- 15s minimum cache
- Full access, including historical archives and bulk export
- Optional pay-as-you-go overage past 50,000 credits (A$0.005/credit, capped)
What things cost
| Category | Credits | Examples |
|---|---|---|
| Standard | 1 | Most live endpoints – gameday, opportunities, racing meetings, odds feed |
| Archive | 5 | Results, odds/CLV history, historical player/team stats – paid tier only |
| Bulk | 25 | /bulk, /export, /odds-feed/racing/history – paid tier only |
106 of 134 endpoints are reachable on the free tier – the rest (28) require the API plan (deep historical archives and bulk exports).
Endpoints by Category
Gameday
8 endpointsPer-event head-to-head odds, alternate lines, best prices across books, player props, live state and AI-generated summaries.
GET/api/v1/gameday/alt-linesFree tier
Alternate-line (non-standard spread/total) odds for events, best price per line plus the full raw line list.
No creditCost concept in this route — auth is monthly-usage based (meta.rate_limit), not per-request credits. event_id does a direct Supabase doc lookup instead of a query. No results returns data: [] (HTTP 200), not 404. Response cached via unstable_cache 300s (free) / 60s (api). CORS open (Access-Control-Allow-Origin: *).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (also accepts `sport`), e.g. `basketball_nba`. Lowercased. |
| sport | string | No | – | Alias for sport_key. |
| event_id | string | No | – | Return alt-lines for a single event only (direct doc lookup). |
| market | string | No | – | Filter lines/best-by-line to a single market key, e.g. `spreads`. Lowercased. |
| limit | number | No | 50 | Max events returned. Clamped to 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/alt-lines?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"sport_title": "NBA",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"commence_time": "2026-09-03T23:10:00Z",
"best_by_line": [
{
"key": "spreads|-4.5",
"market_key": "spreads",
"selection": "Boston Celtics -4.5",
"point": -4.5,
"bookmaker": "sportsbet",
"odds": 1.9,
"bet_link": "https://www.sportsbet.com.au/..."
}
],
"all_lines": [
{
"market_key": "spreads",
"selection": "Boston Celtics -4.5",
"point": -4.5,
"bookmaker": "ladbrokes",
"odds": 1.87,
"bet_link": "https://www.ladbrokes.com.au/..."
}
],
"line_count": 12,
"updated_at": "2026-09-03T10:05:00Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "basketball_nba",
"event_id": null,
"market": null,
"timestamp": "2026-09-03T10:06:00Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/gameday/best-pricesFree tier
Best available price per market/selection per event, aggregated across bookmakers.
Cache 120s (free) / 30s (api) — tighter than most gameday routes since best prices move fastest. `bookmaker_count` reflects how many books were quoting that exact price, not total books on the market.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (also accepts `sport`). |
| sport | string | No | – | Alias for sport_key. |
| event_id | string | No | – | Filter to a single event via query (not a direct doc get, unlike alt-lines). |
| market | string | No | – | Filter the prices array to a single market. Lowercased. |
| limit | number | No | 50 | Max events returned. Clamped to 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/best-prices?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "basketball_nba_20260903_lal_bos",
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"commence_time": "2026-09-03T23:10:00Z",
"prices": [
{
"market": "h2h",
"selection": "Boston Celtics",
"point": null,
"price": 1.65,
"bookmaker": "sportsbet",
"bookmaker_title": "Sportsbet",
"bookmaker_count": 9,
"bet_link": "https://www.sportsbet.com.au/..."
}
],
"updated_at": "2026-09-03T10:04:30Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"markets_filter": null,
"timestamp": "2026-09-03T10:06:00Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/gameday/event/{id}Free tier
Bundled single-event fetch — pulls summary, h2h, best_prices, alt_lines, odds_history, live and props in one call, selectable via `parts`.
400 error if sport_key is missing while parts includes h2h or live. Each requested part is read via Promise.allSettled, so a single collection failure returns null for that part rather than failing the whole request. `parts` output shapes vary per sub-collection (not normalized to a common schema) — summary/h2h/best_prices/alt_lines/odds_history/live are single objects (or null), props is an array.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| id | string | Yes | – | Gameday event id (path segment), e.g. `basketball_nba_20260903_lal_bos`. |
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Required only if `parts` includes h2h or live (they key off `${sportKey}_${eventId}`); also accepts `sport`. |
| sport | string | No | – | Alias for sport_key. |
| parts | string (comma-separated enum) | No | all of: summary,h2h,best_prices,alt_lines,props,odds_history,live | Comma-separated subset of parts to fetch; invalid values are silently dropped. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/event/abc123?sport_key=aussierules_afl"{
"success": true,
"data": {
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"summary": {
"headline": "Celtics look to cover at home against shorthanded Lakers",
"summary": "Boston is 8-2 ATS in its last 10 home games...",
"key_storylines": [
"Lakers missing starting PG",
"Celtics on a 5-game win streak"
],
"generated_at": "2026-09-03T08:00:00Z"
},
"h2h": {
"summary": {
"total_meetings": 12,
"home_wins": 7,
"away_wins": 5
},
"last_meetings": [
{
"date": "2026-02-14",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"home_score": 118,
"away_score": 109
}
]
},
"best_prices": {
"id": "basketball_nba_20260903_lal_bos",
"prices": []
},
"alt_lines": {
"bestByLine": {},
"allLines": []
},
"odds_history": null,
"live": null,
"props": [
{
"id": "prop123",
"playerName": "Jayson Tatum",
"marketKey": "player_points",
"line": 27.5,
"odds": 1.9,
"bookmaker": "sportsbet",
"bet_link": "https://..."
}
]
},
"meta": {
"tier": "free",
"parts": [
"summary",
"h2h",
"best_prices",
"alt_lines",
"props",
"odds_history",
"live"
],
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"timestamp": "2026-09-03T10:06:00Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/gameday/h2hFree tier
Head-to-head historical matchup summaries and recent meeting results between the two teams in an event.
`team` filter is applied client-side after fetching up to limit*2 rows from Supabase, then sliced to `limit` — so a narrow team filter combined with a high limit can under-return relative to what actually exists. Draws is always 0 for sports without draws.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (also accepts `sport`). |
| sport | string | No | – | Alias for sport_key. |
| event_id | string | No | – | Filter to a single event. |
| team | string | No | – | Substring match (case-insensitive) against either home_team or away_team. |
| limit | number | No | 50 | Max rows returned. Clamped to 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/h2h?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "basketball_nba_20260903_lal_bos",
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"sport_title": "NBA",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"commence_time": "2026-09-03T23:10:00Z",
"summary": {
"total_meetings": 12,
"home_wins": 7,
"away_wins": 5,
"draws": 0,
"home_win_pct": 58.3,
"away_win_pct": 41.7
},
"last_meetings": [
{
"date": "2026-02-14",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"home_score": 118,
"away_score": 109,
"venue": "TD Garden"
}
],
"updated_at": "2026-09-03T09:00:00Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"timestamp": "2026-09-03T10:06:00Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/gameday/liveFree tier
Live/in-progress and recently-finished game states — scores, status (pre/live/final), completion flag.
Shortest cache window of the gameday group: 60s free / 15s api, reflecting the need for near-real-time scores. Ordered commence_time descending (most recently started first) unlike other gameday routes which order ascending.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (also accepts `sport`). |
| sport | string | No | – | Alias for sport_key. |
| event_id | string | No | – | Filter to a single event. |
| status | enum(pre|live|final) | No | – | Filter to a specific game status; other values are ignored (no filter applied). |
| limit | number | No | 50 | Max rows returned. Clamped to 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/live?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"commence_time": "2026-09-03T23:10:00Z",
"home_score": 54,
"away_score": 49,
"completed": false,
"status": "live",
"last_update": "2026-09-03T23:45:12Z",
"updated_at": "2026-09-03T23:45:12Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "basketball_nba",
"event_id": null,
"status": "live",
"timestamp": "2026-09-03T23:45:20Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/gameday/propsFree tier
Player prop best-odds, aggregated across bookmakers and grouped by event; underlying storage is one row per (bookmaker, market, outcome) quote.
`limit` counts events, but rows are per-prop-quote, so the route over-fetches up to limit*40 (max 2000) prop rows to try to cover ~limit events after grouping — high-volume slates can still under-return events. Each prop is a best-odds aggregate across books (bet_link points to the best-odds book only, other books in book_count have no exposed URL). Cache floor enforced at >=300s regardless of declared tier value (see CLAUDE.md Firestore-read-cost rule).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (also accepts `sport`). |
| sport | string | No | – | Alias for sport_key. |
| event_id | string | No | – | Filter to a single event. |
| market | string | No | – | Filter to a single prop market key, e.g. `player_points`. Lowercased. |
| player_slug | string | No | – | Filter to a single player by slugified name (lowercase, hyphenated). |
| limit | number | No | 50 | Max EVENTS returned (not prop rows). Clamped to 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/props?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"sport_title": "NBA",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"commence_time": "2026-09-03T23:10:00Z",
"props": [
{
"market_key": "player_points",
"player_name": "Jayson Tatum",
"player_slug": "jayson-tatum",
"line": 27.5,
"side": "over",
"best_odds": 1.95,
"best_bookmaker": "sportsbet",
"book_count": 6,
"bet_link": "https://www.sportsbet.com.au/..."
}
],
"prop_count": 1,
"updated_at": "2026-09-03T10:00:00Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "basketball_nba",
"event_id": null,
"market": null,
"player_slug": null,
"timestamp": "2026-09-03T10:06:00Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/gameday/summariesFree tier
AI-generated pre-game summaries/headlines/storylines and best-market snapshot per event.
Longest cache window of the gameday group: 600s free / 120s api (summaries change slowly, tied to the CLAUDE.md Firestore-read-cost 600s-window rule for gameday-data.ts). event_id does a direct doc get; otherwise a sport-filtered query ordered by commence_time ascending.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (also accepts `sport`). |
| sport | string | No | – | Alias for sport_key. |
| event_id | string | No | – | Direct doc lookup for a single event's summary. |
| limit | number | No | 50 | Max rows returned. Clamped to 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/summaries?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "basketball_nba_20260903_lal_bos",
"sport_key": "basketball_nba",
"sport_title": "NBA",
"home_team": "Boston Celtics",
"away_team": "Los Angeles Lakers",
"commence_time": "2026-09-03T23:10:00Z",
"summary": "Boston enters as 4.5-point favorites off a five-game win streak...",
"headline": "Celtics eye sixth straight win as Lakers battle injuries",
"key_storylines": [
"Lakers PG questionable",
"Celtics 8-2 ATS at home"
],
"best_h2h": {
"selection": "Boston Celtics",
"odds": 1.65,
"bookmaker": "sportsbet"
},
"best_spread": {
"selection": "Boston Celtics -4.5",
"odds": 1.9,
"bookmaker": "ladbrokes"
},
"best_total": {
"selection": "Over 224.5",
"odds": 1.87,
"bookmaker": "sportsbet"
},
"generated_at": "2026-09-03T08:00:00Z",
"updated_at": "2026-09-03T08:00:00Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "basketball_nba",
"event_id": null,
"timestamp": "2026-09-03T10:06:00Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00Z"
}
}
}GET/api/v1/lineupsFree tier
Named team lineups for upcoming fixtures \u2014 the confirmed squad each club names ahead of game day.
Reads from team lineup tables. Post-filters by date/team/round/confirmed since the Supabase read returns all rows. Sorted by date descending. Cached 300s.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | – | Filter by sport_key ("aussierules_afl" or "rugbyleague_nrl"). Omit to return both. |
| date | string | No | – | Filter by match date (YYYY-MM-DD, exact match). |
| team | string | No | – | Filter by team name (partial match, case-insensitive). |
| round | number | No | – | Round number (integer). |
| confirmed | string | No | – | "true" or "false" to filter by confirmation status. |
| limit | number | No | 100 | Max results, clamped 1-200 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/lineups?sport=aussierules_afl"{
"success": true,
"data": [
{
"id": "lineup_afl_20260906_abc",
"gameId": "afl_20260906_abc",
"date": "2026-09-06",
"sportKey": "aussierules_afl",
"homeTeamName": "Collingwood",
"awayTeamName": "Carlton",
"home": {
"players": [
{
"playerId": "p001",
"playerName": "Nick Daicos",
"position": "Midfielder",
"order": 1
}
],
"formation": null
},
"away": {
"players": [
{
"playerId": "p002",
"playerName": "Patrick Cripps",
"position": "Midfielder",
"order": 1
}
],
"formation": null
},
"venue": "MCG",
"venueCity": "Melbourne",
"roundNumber": 23,
"confirmed": true,
"_fetchedAt": "2026-09-05T10:00:00Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"filter": {
"sport": "aussierules_afl",
"date": null,
"team": null,
"round": null,
"confirmed": null
},
"sport_breakdown": {
"aussierules_afl": 1
},
"note": "Named team lineups for upcoming fixtures."
}
}Opportunities
7 endpointsKrok'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.
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 `fetchOpportunitiesData` (src/lib/v1-opportunities-data.ts) — 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 unrelated racing endpoints (`/v1/racing/arbs`, `/v1/racing/movers`), which do not correspond to any of the 14 files audited here — likely stale/aspirational documentation baked into the payload. Rate-limit headers use `limit` (the row cap) rather than the usual free/api tier request cap seen on other routes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| type | enum(all|positive-ev|snipes|middles|racing|playerprops) | No | all | Which 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_key | string | No | – | Filter by sport key. Lowercased. |
| sport | string | No | – | Filter by sport name; uppercased before matching. |
| minvalue | number | No | 0 | Minimum opportunity value/edge threshold (also accepts `min_value`). |
| min_value | number | No | 0 | Alias for minvalue. |
| limit | number | No | 100 | Max rows returned. Clamped to 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities?type=all"{
"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": "/v1/racing/arbs",
"description": "Australian racing arbitrage opportunities",
"parameters": [
"venue",
"race_type (T/H/G)",
"minedge",
"limit"
]
},
{
"path": "/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 freshness stamp is >5min old or with insane odds (isPriceFresh/hasSaneOdds). Betfair Exchange legs are stripped for hobby-equivalent (free) tier via filterExchangeForHobby. `status` (live/upcoming) is derived from commence_time, not stored. Cache 300s both tiers.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key. Lowercased. |
| sport | string | No | – | Filter by sport (also accepts sport_key as fallback); uppercased. |
| minvalue | number | No | 0 | Minimum arb value/edge % (also accepts min_value). |
| min_value | number | No | 0 | Alias for minvalue. |
| bookmaker | string | No | – | Substring match against either leg's bookmaker (case-insensitive). |
| limit | number | No | 100 | Max rows per page. Clamped to 100 (free) / 10000 (api). |
| cursor | string | No | – | Opaque keyset-pagination cursor from a prior response's meta.next_cursor. |
| fields | string (comma-separated) | No | – | Field projection — restrict each row to only the named fields. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities/arbitrage?sport_key=aussierules_afl"{
"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 (free) / 15s (api) vs 300s for arbitrage/middles/positive-ev. Sorted ascending by hold percentage (lowest hold first). Same staleness (12h commence cutoff) and price-freshness (5min) filtering, plus filterExchangeForHobby, as arbitrage. Keyset pagination + field projection supported.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key. Lowercased. |
| sport | string | No | – | Filter by sport (also accepts sport_key fallback); uppercased. |
| minvalue | number | No | 0 | Minimum value threshold (also accepts min_value); checks value/holdPct alias chain. |
| min_value | number | No | 0 | Alias for minvalue. |
| max_hold | number | No | – | Upper bound on hold percentage (holdPct or value). |
| bookmaker | string | No | – | Substring match against either leg's bookmaker. |
| limit | number | No | 100 | Max rows per page. Clamped to 100 (free) / 10000 (api). |
| cursor | string | No | – | Opaque keyset-pagination cursor. |
| fields | string (comma-separated) | No | – | Field projection list. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities/low-holds?sport_key=aussierules_afl"{
"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 + 5min freshness filtering as arbitrage. No filterExchangeForHobby call here (unlike arbitrage/low-holds), so Betfair Exchange legs are NOT stripped for free tier on this route. Cache 300s both tiers.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key. Lowercased. |
| sport | string | No | – | Filter by sport (also accepts sport_key fallback); uppercased. |
| minvalue | number | No | 0 | Minimum middle value (also accepts min_value). |
| min_value | number | No | 0 | Alias for minvalue. |
| limit | number | No | 100 | Max rows per page. Clamped to 100 (free) / 10000 (api). |
| cursor | string | No | – | Opaque keyset-pagination cursor. |
| fields | string (comma-separated) | No | – | Field projection list. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities/middles?sport_key=aussierules_afl"{
"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 ev_percentage in memory rather than at the DB level, because there's no index for a global ev_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 free / 15s api.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key. Lowercased. |
| sport | string | No | – | Filter by sport (also accepts sport_key fallback); lowercased (unlike other opportunities routes which uppercase). |
| market | string | No | – | Exact match against market_key, e.g. `player_points`. |
| player | string | No | – | Substring match against player_name (case-insensitive). |
| bookmaker | string | No | – | Substring match against bookmaker. |
| min_ev | number | No | 0 | Minimum ev_percentage threshold. |
| include_stats | boolean | No | false | When `true` (and tier is not free), joins in historical_stats (hit rates, streaks) per prop from player_props_stats. |
| limit | number | No | 100 | Max rows returned. Clamped to 100 (free) / 10000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities/player-props?sport_key=aussierules_afl"{
"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",
"bet_link": "https://www.sportsbet.com.au/...",
"ev_percentage": 7.8,
"commence_time": "2026-09-03T23:10:00Z",
"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. Betfair 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 + 5min freshness filtering as arbitrage/middles. No filterExchangeForHobby applied. Cache 300s both tiers.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key. Lowercased. |
| sport | string | No | – | Filter by sport (also accepts sport_key fallback); uppercased. |
| minvalue | number | No | 0 | Minimum EV value (also accepts min_value). |
| min_value | number | No | 0 | Alias for minvalue. |
| bookmaker | string | No | – | Substring match against either leg's bookmaker. |
| min_odds | number | No | 0 | Minimum odds1 threshold (also accepts minOdds). |
| minOdds | number | No | 0 | Alias for min_odds. |
| limit | number | No | 100 | Max rows per page. Clamped to 100 (free) / 10000 (api). |
| cursor | string | No | – | Opaque keyset-pagination cursor. |
| fields | string (comma-separated) | No | – | Field projection list. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities/positive-ev?sport_key=aussierules_afl"{
"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 getAdminDb() (Firestore) as its primary path, with an isSupabase('sgm_picks') feature-flagged Supabase attempt first that falls back to Firestore on failure — returns 503 'Database unavailable' if Firestore admin isn't initialized, even if Supabase would have worked (503 check happens before the Supabase 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 free / 15s api. No meta.limit/requested_limit/next_cursor fields in the response (unlike arbitrage/middles/etc).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | – | Filter by sport key (also accepts sport_key). Lowercased. |
| sport_key | string | No | – | Alias for sport. |
| tier | enum(safe|value|longshot) | No | – | Filter to a risk tier; any other value is ignored (no filter). |
| min_confidence | number | No | 1 | Minimum confidence score, clamped to 1-5. |
| limit | number | No | 20 | Max rows returned. Clamped to 50 (free) / 1000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities/sgm-picks?sport=aussierules_afl"{
"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": [
{
"market": "player_points",
"selection": "Jayson Tatum Over 27.5",
"odds": 1.9
},
{
"market": "h2h",
"selection": "Boston Celtics",
"odds": 1.65
}
],
"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"
}
}
}Racing
17 endpointsThe full Australian racing stack — thoroughbred, harness and greyhound. Meetings and runner fields, form, sectionals, ratings, market movers, exchange arbs, tote pools and results.
GET/api/v1/racing/arbsFree tier
Live cross-bookmaker arbitrage opportunities on Australian racing (win markets), read from the racing_opportunities/arbs doc (Supabase-first, Firestore fallback).
Reads a single pre-aggregated doc (racing_opportunities/arbs), not a live scan; filters applied in JS after fetch. Cache 60s free / 15s api. Legacy inline auth boilerplate (not using shared authorizeRacing helper).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| venue | string | No | – | Case-insensitive substring match on meeting venue name. |
| race_type | string | No | – | Exact match on race type code (e.g. T/H/G) as stored on the arb record. |
| minedge | number | No | 0 | Minimum arb edge (as a fraction, e.g. 0.03 = 3%) to include. |
| limit | integer | No | 50 | Max rows returned; capped at 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/arbs?venue=Randwick"{
"success": true,
"data": [
{
"id": "arb_20260903_randwick_r5_furioustempo",
"venue": "Randwick",
"race_number": 5,
"race_name": "Group 3 Show County Quality",
"race_type": "T",
"jump_time": "2026-09-03T04:35:00.000Z",
"runner": "Furious Tempo",
"arb_type": "win",
"leg1_bookmaker": "Sportsbet",
"leg1_odds": 4.8,
"leg1_bet_link": "https://www.sportsbet.com.au/search?q=Furious%20Tempo",
"leg2_bookmaker": "Betfair",
"leg2_odds": 5.4,
"leg2_bet_link": "https://www.betfair.com.au/exchange/plus/search?q=Furious%20Tempo",
"edge": 0.0231,
"stake1_pct": 52.9,
"stake2_pct": 47.1,
"all_legs": null,
"detected_at": "2026-09-03T03:58:12.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/connectionsFree tier
Jockey/trainer profile lookup by name — Betfair form win-strike-rate, FormFav per-track stats, and Racing.com career/current-season profile, merged.
Names always echo back as rows with null strike rates when unmatched — `meta.matched` (not `count`) is the true 'has data' signal. Slug resolution tries multiple candidate spellings (connectionSlugCandidates); dedupes by resolved slug.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| names | string (comma-separated) | Yes | – | One or more jockey/trainer names to look up (max 50). |
| type | string enum | No | both | One of jockey | trainer | both. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/connections?names=value"{
"success": true,
"data": {
"jockeys": [
{
"name": "James McDonald",
"slug": "james-mcdonald",
"matched_slug": "james-mcdonald",
"win_strike_rate": 24.6,
"runs": 512,
"wins": 126,
"track_stats": [
{
"venue": "Randwick",
"total_starts": 88,
"wins": 24,
"places": 41,
"win_rate": 27.3,
"place_rate": 46.6
}
],
"racing_com": {
"code": "j4821",
"career_wins": 1834,
"career_starts": 8422,
"win_percent": 21.8,
"place_percent": 47.2,
"recent_win_percent": 26.1,
"current_wins": 92,
"current_starts": 344,
"current_seconds": 58,
"current_thirds": 44,
"current_vic_metro_starts": 12,
"current_vic_metro_wins": 3,
"current_vic_country_starts": 0,
"current_vic_country_wins": 0,
"current_sa_metro_starts": 0,
"current_sa_metro_wins": 0,
"current_sa_country_starts": 0,
"current_sa_country_wins": 0,
"victorian_ranking": null,
"location": "Sydney, NSW",
"apprentice": false,
"weight_average": 57.2
}
}
],
"trainers": []
},
"meta": {
"count": 1,
"matched": 1,
"empty": false,
"tier": "free",
"type": "jockey",
"names": [
"James McDonald"
],
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/connections/combinationsFree tier
Win-strike-rate stats for jockey/trainer/horse combinations, computed from the last 90 days of racing_historical_results.
Computed in-process over up to 5000 racing_historical_results rows from the last 90 days (Supabase racing_historical_results, Firestore fallback) — not a precomputed table. Sorted by wins desc then runs desc.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string enum | No | jockey_trainer | One of jockey_trainer | jockey_horse | trainer_horse — which combo pair to aggregate. |
| names | string (comma-separated) | No | – | Filter combos where either side's name contains one of these (case-insensitive); max 50 entries. |
| limit | integer | No | 50 | Max rows returned; capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/connections/combinations?type=jockey_trainer"{
"success": true,
"data": [
{
"primary": "j mcdonald",
"secondary": "c waller",
"runs": 41,
"wins": 11,
"win_strike_rate": 26.83
},
{
"primary": "j mcdonald",
"secondary": "j cummings",
"runs": 22,
"wins": 5,
"win_strike_rate": 22.73
}
],
"meta": {
"count": 2,
"empty": false,
"tier": "free",
"type": "jockey_trainer",
"names": null,
"window": "90 days",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/connections/season-statsFree tier
Per-season (Aug–Jul AU racing season) jockey/trainer stats broken down by venue and race type, from racing_results_flat.
503 'Season stats require Supabase backend' if DATA_BACKEND isn't Supabase for 'racing' — this endpoint has NO Firestore fallback. Queries racing_results_flat with .ilike on jockey/trainer column.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | Yes | – | Jockey or trainer name (case-insensitive). |
| type | string enum | No | jockey | One of jockey | trainer. |
| season | string | No | current season | Season label e.g. '2025/26', or a single year (interpreted as Aug prevYear–Jul year). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/connections/season-stats?name=value"{
"success": true,
"data": {
"name": "James McDonald",
"type": "jockey",
"season": "2025/26",
"overall": {
"starts": 344,
"wins": 92,
"places": 194,
"win_rate": 26.74,
"place_rate": 56.4
},
"by_venue": [
{
"venue": "randwick",
"starts": 88,
"wins": 24,
"places": 48,
"win_rate": 27.27,
"place_rate": 54.55
},
{
"venue": "rosehill-gardens",
"starts": 61,
"wins": 15,
"places": 33,
"win_rate": 24.59,
"place_rate": 54.1
}
],
"by_type": [
{
"race_type": "T",
"starts": 344,
"wins": 92,
"places": 194,
"win_rate": 26.74,
"place_rate": 56.4
}
]
},
"meta": {
"tier": "free",
"name": "James McDonald",
"type": "jockey",
"season": "2025/26",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/futuresFree tier
Upcoming major-race futures (Melbourne Cup, Cox Plate, Golden Rose etc.) merged from PointsBet futures markets and Amused/BlackStream racing feeds.
Venue is GUESSED from race/competition name text via a hardcoded RACE_VENUE map — not authoritative. Only future dates (startTime >= today) are queried. Uses shared authorizeRacing() helper (../_shared.ts).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| venue | string | No | – | Substring match on inferred venue (mapped from race/competition name via a well-known-race lookup table). |
| race_type | string enum | No | – | One of T | H | G. |
| date | string (YYYY-MM-DD) | No | – | Filter to a specific date. When omitted, returns all meetings across all dates. |
| lite | boolean | No | false | When '1' or 'true', skips heavy enrichments (form, stats, sectionals, videos) for faster responses. |
| limit | integer | No | 50 | Max rows returned; capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/futures?venue=Randwick"{
"success": true,
"data": [
{
"date": "2026-11-03",
"venue": "Flemington",
"race_name": "Melbourne Cup",
"race_type": "T",
"runners": [
{
"name": "Vauban",
"odds": 8.5,
"bookmaker": "PointsBet"
},
{
"name": "Absurde",
"odds": 12,
"bookmaker": "PointsBet"
}
],
"prize_money": null,
"distance": null,
"conditions": null,
"bookmakers": [
"PointsBet",
"Amused"
]
}
],
"meta": {
"count": 1,
"sources": [
"PointsBet",
"Amused"
],
"tier": "free",
"limit": 50,
"requested_limit": 50,
"timestamp": "2026-09-03T04:00:00.000Z"
}
}GET/api/v1/racing/internationalAPI plan
UK/IE and other international racecards and results, from racing_intl_racecards / racing_intl_results (The Racing API sourced).
Built on the shared makeV1Route() factory (v1-collection-route.ts) with feature: 'historical' — this is the ONLY racing endpoint that is NOT free-tier (historical/bulk_export are the only two paid-only features). 204 status + credit refund on empty result set.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string enum | No | racecards | One of racecards | results — which collection to query. |
| date | string (YYYY-MM-DD) | No | – | Exact-match filter on race date. |
| limit | integer | No | 100 | Max rows returned; capped at 50 (free) / 200 (api). Note: gated to api-tier anyway via the 'historical' feature. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/international?type=racecards"{
"success": true,
"data": [
{
"date": "2026-09-03",
"venue": "Ascot",
"country": "GB",
"race_name": "3:35 Ascot - Handicap",
"race_class": "Class 4",
"distance_f": 8,
"runners": [
{
"name": "Northern Lad",
"jockey": "R Moore",
"trainer": "C Appleby",
"draw": 3,
"or_rating": 78
}
]
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 100,
"requested_limit": 100,
"type": "racecards",
"date": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 1998,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/meetingsFree tier
The full racing board \u2014 today's (or filtered) AU/NZ/UK-IE race meetings with races and runners, heavily enriched (TAB cards, Racing.com sectionals/videos, FormFav form/stats, results, Betfair Exchange snapshots, cross-book market movers).
By far the heaviest endpoint — up to 6 sequential/parallel enrichment joins per meeting (TAB, intl racecards, Racing.com sectionals/videos, form, stats, results/TABNZ/Betfair/PuntersEdge). Cache 120s free / 30s api (both below the 300s CLAUDE.md floor per v1Revalidate() clamp logic). Full runner object has ~80 optional keys — stripped of nulls by default; use ?full=1 to restore. UK/IE meetings synthesized as type 'R' but counted as 'T' unless H/G explicitly requested.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | – | Filter to a specific date. When omitted, returns all meetings across all dates. |
| race_type | string enum | No | – | One of T | H | G. Alias: `type`. |
| type | string enum | No | – | Alias for race_type. |
| venue | string | No | – | Case-insensitive substring match on venue name. |
| jurisdiction | string | No | – | State/tote-jurisdiction code (e.g. NSW, VIC). Alias: `state`. |
| state | string | No | – | Alias for jurisdiction. |
| limit | integer | No | 50 | Max meetings returned; capped at 50 (free) / 1000 (api). |
| full | boolean ("1") | No | 0 | When '1', returns the explicit-null shape (all ~80 runner keys present even if null); default strips null/empty-array fields to shrink payload. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/meetings?date=2026-09-01"{
"success": true,
"data": [
{
"id": "tab_1234567",
"meeting_key": "2026-09-03_flemington",
"venue": "Flemington",
"state": "VIC",
"country": null,
"tote_jurisdiction": "VIC",
"type": "T",
"date": "2026-09-03",
"race_count": 8,
"next_jump": "2026-09-03T04:35:00.000Z",
"weather": {
"summary": "Partly cloudy",
"temp_c": 16,
"rain_24h_mm": 0,
"wind_kmh": 14,
"wind_dir": "SW"
},
"track_hint": "Good 4",
"prize_money": 180000,
"races": [
{
"id": "1234567_5",
"race_key": "2026-09-03_flemington_5",
"number": 5,
"jump_time": "2026-09-03T04:35:00.000Z",
"name": "Turnbull Stakes",
"distance": 2000,
"race_class": "Group 1",
"status": "open",
"track_condition": "Good 4",
"runner_count": 10,
"allowed_bet_types": [
"WIN",
"PLC",
"EXA",
"TRI"
],
"runners": [
{
"id": "r1",
"number": 3,
"name": "Zaaki",
"barrier": 6,
"bib": null,
"gear": null,
"jockey": "J Bowman",
"trainer": "A Freedman",
"weight": 58.5,
"is_scratched": false,
"best_win": 3.2,
"best_win_bookmaker": "Sportsbet",
"odds": [
{
"bookmaker": "Sportsbet",
"win": 3.2,
"place": 1.5,
"bet_link": "https://www.sportsbet.com.au/search?q=Zaaki"
},
{
"bookmaker": "Betfair",
"win": 3.4,
"matched": 42000,
"bet_link": "https://www.betfair.com.au/exchange/plus/search?q=Zaaki"
}
]
}
]
}
]
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/moversFree tier
Live odds steamers/drifters (significant price movements) across AU racing markets, read from racing_opportunities/movers.
Reads a single doc combining `movers` + `drifters` arrays. Falls back Firestore→Supabase if the Supabase doc is missing or both arrays are empty. Legacy inline auth (not shared _shared.ts helper).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| venue | string | No | – | Case-insensitive substring match on meeting venue. |
| race_type | string | No | – | Exact match on race type code. |
| movement_type | string enum | No | – | One of steamer | drifter. |
| min_movement | number | No | 0 | Minimum absolute movement percentage to include. |
| limit | integer | No | 50 | Max rows returned; capped at 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/movers?venue=Randwick"{
"success": true,
"data": [
{
"race_id": "1234567_5",
"venue": "Caulfield",
"race_number": 5,
"race_name": "Memsie Stakes",
"race_type": "T",
"jump_time": "2026-09-03T05:10:00.000Z",
"runner": "Alligator Blood",
"runner_number": 4,
"bookmaker": "Ladbrokes",
"bet_link": "https://www.ladbrokes.com.au/search?q=Alligator%20Blood",
"opening_odds": 6.5,
"current_odds": 4.2,
"movement_pct": -35.4,
"movement_type": "steamer"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/odds-historyFree tier
Per-runner price-movement curves (open \u2192 every fluctuation \u2192 close/SP/BSP) per bookmaker for a meeting/race/runner, built from race card data, historical results and odds snapshots.
Tier detail: free (but >14 days back requires api tier)
Free tier is hard-clamped to the last 14 days regardless of the account's `historicalDays` window (extra check beyond the standard historical-clamp gate) — returns 402 with X-Krok-Feature: historical. Tote/SP book keys (tote, vrc, racenet, etc.) are excluded from bookFlucs. Falls back card→historical-results→live-snapshot-only in that order per meeting. Uses shared _shared.ts auth (feature racing_premium, cost 5).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | today (Sydney meeting date) | Meeting date to fetch. |
| venue | string | No | – | Case-insensitive substring match on venue. |
| raceNumber | integer | No | – | Filter to a single race number. Alias: `race`. |
| race | integer | No | – | Alias for raceNumber. |
| runner_slug | string | No | – | Filter to a single runner by slugified name. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/odds-history?date=today%20(Sydney%20meeting%20date)"{
"success": true,
"data": [
{
"id": "tab_1234567",
"venue": "Flemington",
"date": "2026-09-03",
"races": [
{
"number": 5,
"name": "Turnbull Stakes",
"status": "closed",
"start_time": "2026-09-03T04:35:00.000Z",
"runners": [
{
"number": 3,
"name": "Zaaki",
"is_scratched": false,
"open": 3.6,
"sp": 3.2,
"bsp": 3.15,
"flucs": [
{
"timestamp": "2026-09-03T02:00:00.000Z",
"odds": 3.6
},
{
"timestamp": "2026-09-03T04:30:00.000Z",
"odds": 3.2
}
],
"bookFlucs": {
"sportsbet": [
{
"timestamp": "2026-09-03T02:00:00.000Z",
"odds": 3.7
},
{
"timestamp": "2026-09-03T04:30:00.000Z",
"odds": 3.3
}
]
}
}
]
}
]
}
],
"meta": {
"count": 1,
"date": "2026-09-03",
"venue": null,
"race_number": 5,
"runner_slug": null,
"tier": "free",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
},
"source": "card"
}
}GET/api/v1/racing/predictionsFree tier
AI-powered win/place probability predictions for AU/NZ races, from the racing predictions model.
Built on makeV1Route() factory. Sorted by model_rank asc then win_probability desc. Fetches limit*4 rows when venue/code/horse filters are set (JS post-filter), capped at the requested limit afterward.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | today | Race date to filter. |
| venue | string | No | – | Case-insensitive substring match on venue. |
| code | string enum | No | – | Racing code: thoroughbred | harness | greyhound. |
| horse | string | No | – | Case-insensitive substring match on horse name. |
| limit | integer | No | 200 | Max rows returned; capped at 200 (free) / 1000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/predictions?date=today"{
"success": true,
"data": [
{
"id": "bf_pred_20260903_flemington_5_3",
"date": "2026-09-03",
"venue": "Flemington",
"race_no": 5,
"horse_name": "Zaaki",
"win_probability": 0.31,
"place_probability": 0.58,
"model_rank": 1,
"code": "thoroughbred"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 200,
"requested_limit": 200,
"date": "2026-09-03",
"venue": null,
"code": null,
"horse": null,
"source": "Krok Odds Predictions",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/ratingsFree tier
KrokBot AI pick detail for a single race — per-runner win probability/rating, field analysis verdicts, and Betfair money-flow tip signals, from racing_ai_picks.
404 with meta.status='not_yet_populated' if no pick exists for the race yet. `model` field is deliberately null (internal, not exposed via public API). Betfair money-flow tip join is best-effort (venue+raceNumber+sport, disambiguated by jumpTime proximity) — failure degrades gracefully, doesn't fail the request.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| race_id | string | Yes | – | Direct racing_ai_picks doc id lookup. |
| meeting_id | string | Yes | – | Meeting id, used with race_number as a fallback query. |
| race_number | integer | Yes | – | Race number within the meeting, used with meeting_id. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/ratings?race_id=abc123&meeting_id=abc123&race_number=10"{
"success": true,
"data": {
"race_id": "1234567_5",
"meeting_id": "1234567",
"race_name": "Turnbull Stakes",
"race_number": 5,
"venue": "Flemington",
"state": "VIC",
"race_type": "T",
"jump_time": "2026-09-03T04:35:00.000Z",
"date": "2026-09-03",
"tier": "feature",
"score": 82.4,
"confidence": "high",
"top_pick": {
"slug": "zaaki",
"name": "Zaaki",
"bet_link": "https://www.sportsbet.com.au/search?q=Zaaki"
},
"runners": [
{
"slug": "zaaki",
"name": "Zaaki",
"win_prob": 0.31,
"fair_odds": 3.23,
"confidence": "high",
"rank": 1,
"edge_pct": 4.2,
"value": true,
"money_share": 0.28,
"money_delta": 0.03,
"rating": 88.5
}
],
"risk": "low",
"pick_rationale": "Strong recent form at set weights over 2000m.",
"track_condition": "Good 4",
"bestWin": 3.2,
"bestWinBookmaker": "Sportsbet",
"value_picks": [],
"market_mover": null
},
"meta": {
"count": 8,
"empty": false,
"tier": "free",
"race_id": "1234567_5",
"meeting_id": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/resultsFree tier
Archive query across settled race results (Betfair BSP-based, lighter fields than the date-specific endpoint) with filters by track, race type, runner and date range.
Tier detail: free (date-range filters `since`/`until` require api tier via the 'historical' feature)
Explicit CREDIT_COST=5 archive endpoint; refunds the full credit cost when the result set is empty. Setting `since`/`until` on a free-tier key returns a 402 tier-gate error for the 'historical' feature BEFORE the per-key clamp runs. `track`/`runner_slug`/`race_name` filters are applied client-side after an over-fetch (up to 2000 rows).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| race_type | string enum | No | – | One of T | H | G. |
| track_slug | string | No | – | Exact match on venue slug. |
| track | string | No | – | Case-insensitive substring match on track name. |
| race_name | string | No | – | Case-insensitive substring match on race name. |
| runner_slug | string | No | – | Exact match on a runner's slug within the race. |
| since | string (YYYY-MM-DD) | No | – | Results on/after this date. Requires 'historical' feature (api tier); also clamped per-key by verification.historicalDays. |
| until | string (YYYY-MM-DD) | No | – | Results on/before this date. Requires 'historical' feature (api tier). |
| limit | integer | No | 50 | Max rows returned; capped at 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/results?race_type=value"{
"success": true,
"data": [
{
"id": "2026-09-03_flemington_5",
"date": "2026-09-03",
"track": "Flemington",
"track_slug": "flemington",
"state": "VIC",
"race_no": 5,
"race_type": "T",
"distance_m": 2000,
"race_name": "Turnbull Stakes",
"winning_time": 121.4,
"mile_rate": null,
"winner": {
"tab_number": 3,
"name": "Zaaki",
"slug": "zaaki",
"jockey": "J Bowman",
"trainer": "A Freedman",
"win_bsp": 3.15
},
"runners": [
{
"tab_number": 3,
"name": "Zaaki",
"slug": "zaaki",
"finish_position": 1,
"win_result": 1,
"win_bsp": 3.15,
"jockey": "J Bowman",
"trainer": "A Freedman"
}
],
"going": "Good 4",
"race_class": "Group 1",
"ingested_at": "2026-09-03T05:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"race_type": null,
"track_slug": null,
"race_name": null,
"runner_slug": null,
"since": null,
"until": null,
"data_note": "This endpoint returns Betfair BSP-based results (limited fields). For richer results including margins, times and full connections, use the date-specific endpoint /api/v1/racing/results/{date}.",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 937,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/results/{date}Free tier
Full settled race results for every meeting on a single date — finishing order, margins, times, BSP, jockey/trainer — merged with TAB tote pools and dividends.
Tier detail: free (subject to per-key historicalDays clamp)
Same `{date}_{venueSlug}_{raceNo}` race_key/meeting_key join keys as /racing/meetings. Sorts results by raceNo in JS (NOT in SQL) to avoid a full seq-scan on the unindexed data->>raceNo JSONB path — deliberate perf workaround. Cache 300s (respects the CLAUDE.md floor).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | – | Meeting date to fetch results for; 400 if not a valid YYYY-MM-DD string. |
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| venue | string | No | – | Case-insensitive substring match on track name/slug. Alias: `track`. |
| track | string | No | – | Alias for venue. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/results/2026-09-01?venue=Randwick"{
"success": true,
"data": [
{
"id": "2026-09-03_flemington_5",
"race_key": "2026-09-03_flemington_5",
"meeting_key": "2026-09-03_flemington",
"date": "2026-09-03",
"track": "Flemington",
"track_slug": "flemington",
"state": "VIC",
"race_no": 5,
"race_type": "T",
"distance_m": 2000,
"race_name": "Turnbull Stakes",
"going": "Good 4",
"race_class": "Group 1",
"status": "settled",
"winner": {
"tab_number": 3,
"name": "Zaaki",
"slug": "zaaki",
"jockey": "J Bowman",
"trainer": "A Freedman",
"win_bsp": 3.15
},
"pools": [
{
"pool_type": "WIN",
"total": 184200
}
],
"dividends": [
{
"pool_type": "WIN",
"tab_number": 3,
"dividend": 3.4
}
],
"runners": [
{
"tab_number": 3,
"name": "Zaaki",
"finish_position": 1,
"margin": 0,
"win_result": 1,
"win_bsp": 3.15,
"jockey": "J Bowman",
"trainer": "A Freedman",
"barrier": 6,
"weight": "58.5kg"
},
{
"tab_number": 7,
"name": "Buffalo River",
"finish_position": 2,
"margin": 0.8,
"win_result": 0,
"win_bsp": 6.4,
"jockey": "M Zahra",
"trainer": "C Maher",
"barrier": 2,
"weight": "56kg"
}
],
"exotic_pools": [
{
"type": "QUINELLA",
"total": 42100
}
],
"ingested_at": "2026-09-03T05:00:00.000Z"
}
],
"meta": {
"count": 1,
"date": "2026-09-03",
"venue": null,
"tier": "free",
"timestamp": "2026-09-03T05:30:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/runner-formFree tier
A single runner's recent race history (form) across thoroughbred/harness/greyhound codes, with joined Racing.com sectionals and a Brightcove replay-video fallback resolver.
Per memory (runner-form Supabase path): the Supabase branch (`runner_recent_races` table) is gated behind isSupabase('runner-form') and was reported BLOCKED/undeployed as of the last audit — may silently fall back to the Firestore `runner_historical_stats/{sport}__{slug}/recent_races` subcollection path. Sectionals join is a single best-effort query per request (ilike on horseName).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| runner_slug | string | Yes | – | Slugified runner name to look up. |
| sport_key | string enum | No | all three codes | One of racing_T | racing_H | racing_G. Alias: `sport`. |
| sport | string enum | No | – | Alias for sport_key. |
| since | string (YYYY-MM-DD) | No | – | Only races on/after this date; clamped by the key's historicalDays window. |
| until | string (YYYY-MM-DD) | No | – | Only races on/before this date. |
| limit | integer | No | 50 | Max rows returned; capped at 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/runner-form?runner_slug=value"{
"success": true,
"data": [
{
"race_id": "2026-08-20_randwick_4",
"sport_key": "racing_T",
"runner_slug": "zaaki",
"date": "2026-08-20",
"track": "Randwick",
"race_no": 4,
"race_name": "Warwick Stakes",
"distance": 1400,
"win_result": 1,
"place_result": 1,
"win_bsp": 2.8,
"tab_number": 5,
"jockey": "J Bowman",
"trainer": "A Freedman",
"barrier": 3,
"finish_position": 1,
"margin": 1.2,
"replay_video": "https://videos.krokodds.com.au/replay/2026-08-20_randwick_4.m3u8",
"sectionals": {
"l600": 34.1,
"l400": 22.4,
"l200": 11.3,
"speed": {
"early": 58.2,
"mid": 59.8,
"late": 61.4,
"overall": 59.9,
"peak": 62.1
},
"closing_ratio": 1.04,
"runs_sampled": 8
}
}
],
"meta": {
"count": 1,
"empty": false,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"runner_slug": "zaaki",
"sport_key": null,
"since": null,
"until": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/runner-statsFree tier
Aggregated career/split stats for a single runner — win/place rates by track, distance, going, track+distance combo, and prep-stage (first-up/second-up/in-prep), plus sectionals and career prize money.
404 with meta.status='not_yet_populated' if no historical record AND no sectionals row exist. When sport_key is omitted, probes racing_T → racing_H → racing_G in order and returns the first with a career record (falls further back to racing_com_sectionals-only if no fetchRunnerStats hit).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| runner_slug | string | Yes | – | Slugified runner name to look up. |
| sport_key | string enum | No | probes all three in order | One of racing_T | racing_H | racing_G. Alias: `sport`. |
| sport | string enum | No | – | Alias for sport_key. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/runner-stats?runner_slug=value"{
"success": true,
"data": {
"runner_slug": "zaaki",
"sport_key": "racing_T",
"historical": {
"name": "Zaaki",
"starts": 34,
"wins": 11,
"places": 20,
"win_rate": 32.35,
"place_rate": 58.82,
"avg_win_bsp": 4.1,
"last_track": "Randwick",
"last_date": "2026-08-20"
},
"career_prize_money": 8420000,
"sectionals": {
"avg_l600": 34.5,
"avg_l400": 22.8,
"avg_l200": 11.5,
"closing_ratio": 1.03,
"avg_speed_early": 57.9,
"avg_speed_mid": 59.4,
"avg_speed_late": 61,
"avg_overall_speed": 59.5,
"avg_peak_speed": 61.8
},
"recent_races": [
{
"date": "2026-08-20",
"venue": "Randwick",
"finish": 1,
"position": 1,
"distance": 1400,
"speed": 61.2
}
],
"splits": {
"by_track": [
{
"bucket": "Randwick",
"starts": 9,
"wins": 4,
"places": 6,
"win_rate": 44.4,
"place_rate": 66.7,
"avg_bsp": 3.6
}
],
"by_distance": [
{
"bucket": "1400m",
"starts": 12,
"wins": 5,
"places": 8,
"win_rate": 41.7,
"place_rate": 66.7,
"avg_bsp": 3.9
}
],
"by_going": [
{
"bucket": "Good",
"starts": 20,
"wins": 8,
"places": 13,
"win_rate": 40,
"place_rate": 65,
"avg_bsp": 3.8
}
],
"by_track_distance": [],
"by_prep_stage": {
"first_up": {
"bucket": "first_up",
"starts": 6,
"wins": 2,
"places": 4,
"win_rate": 33.3,
"place_rate": 66.7,
"avg_bsp": 4.5
},
"second_up": {
"bucket": "second_up",
"starts": 6,
"wins": 3,
"places": 4,
"win_rate": 50,
"place_rate": 66.7,
"avg_bsp": 3.2
},
"in_prep": {
"bucket": "in_prep",
"starts": 22,
"wins": 6,
"places": 12,
"win_rate": 27.3,
"place_rate": 54.5,
"avg_bsp": 4.4
}
},
"class_change": null,
"avg_days_between_starts": 28,
"spell_length": 84,
"form_string": "1-2x14",
"best_bsp": 2.1,
"best_bsp_label": "2026-06-14 Rosehill",
"total_analysed": 34
}
},
"meta": {
"tier": "free",
"runner_slug": "zaaki",
"sport_key": "racing_T",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/sectionals/{horseCode}Free tier
Racing.com sectional times (L600/L400/L200, speed map) and profile (condition splits, first/second/third-up stats, sire progeny) for a horse, keyed by Racing.com's own horse code.
404 if BOTH racing_com_sectionals and racing_com_profiles docs are missing for that code. Cache 600s — sectionals are backfilled post-race and slow-moving. Uses the shared _shared.ts authorizeRacing() with default feature 'racing'.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| horseCode | string | No | – | Racing.com horse code (alphanumeric/underscore/hyphen, 1-64 chars); 400 if it fails that pattern. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/sectionals/H123456"{
"success": true,
"data": {
"horse_code": "h284719",
"name": "Zaaki",
"sectionals": {
"run_count": 8,
"l600": 34.1,
"l400": 22.4,
"l200": 11.3,
"speed": {
"early": 58.2,
"mid": 59.8,
"late": 61.4,
"overall": 59.9,
"peak": 62.1
},
"closing_ratio": 1.04
},
"profile": {
"condition_splits": {
"firm": "2:1-0-1",
"good": "24:9-5-3",
"soft": "6:1-2-1",
"heavy": "2:0-0-1",
"wet": "8:1-2-2"
},
"first_up": "6:2-0-1",
"second_up": "6:3-1-0",
"third_up": "5:1-1-1",
"winning_range": "1400m-2000m",
"days_since_last_win": 96,
"career_stats": "34:11-9-6",
"sire_progeny_dry": "412:78-65-59",
"sire_progeny_wet": "88:14-11-9"
}
},
"meta": {
"tier": "free",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}GET/api/v1/racing/tote-poolsFree tier
Parimutuel (tote) pool totals, dividends and multi-leg exotic pools per race for a meeting day.
Cache is 60s (below the 300s CLAUDE.md floor, but v1Revalidate() enforces the floor at the framework level regardless of the declared constant). Dividends array is typically empty pre-race and only populates once TAB settles the pool.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | today (Sydney meeting date) | Meeting date to fetch tote pool data for. |
| venue | string | No | – | Case-insensitive substring match on venue. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/tote-pools?date=today%20(Sydney%20meeting%20date)"{
"success": true,
"data": [
{
"id": "tab_1234567",
"venue": "Flemington",
"state": "VIC",
"type": "T",
"date": "2026-09-03",
"tote_jurisdiction": "VIC",
"exotic_pools": [
{
"type": "QUADDIE",
"legs": [
4,
5,
6,
7
],
"total": 612400,
"jackpot": false
}
],
"races": [
{
"number": 5,
"name": "Turnbull Stakes",
"distance": 2000,
"status": "open",
"start_time": "2026-09-03T04:35:00.000Z",
"pools": [
{
"pool_type": "WIN",
"total": 184200
},
{
"pool_type": "PLACE",
"total": 96700
}
],
"dividends": []
}
]
}
],
"meta": {
"count": 1,
"date": "2026-09-03",
"venue": null,
"tier": "free",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01"
}
}
}Odds Feed
14 endpointsRaw per-bookmaker prices as scraped, before any analysis layer. `/scraped-odds/*` paths are aliases of the equivalent `/odds-feed/*` route.
GET/api/v1/odds-feed/bookmakersFree tier
Catalogue of bookmakers KrokOdds sources directly (pure registry read, no DB hit).
Zero-cost registry read — no database hit. Cache 300s. `feeds[].status` is 'pending' when the sync is written/merged but the Cloud Function is not yet deployed — surfaced rather than hidden. `aliases` = white-label brands accepted by `?bookmaker=` on other endpoints, served from the parent book's pricing backend. meta.redistributable is @deprecated in favour of meta.direct_scrape (removal 2027-02-01).
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/bookmakers"{
"success": true,
"data": [
{
"key": "sportsbet",
"name": "Sportsbet",
"type": "corporate",
"feeds": [
{
"kind": "sports",
"status": "live"
},
{
"kind": "racing",
"status": "live"
}
],
"racing": true,
"sports": true,
"aliases": []
},
{
"key": "betmakers",
"name": "BetMakers Nimbus",
"type": "platform",
"feeds": [
{
"kind": "sports",
"status": "live"
},
{
"kind": "racing",
"status": "pending"
}
],
"racing": false,
"sports": true,
"aliases": [
{
"key": "unibet",
"name": "Unibet"
},
{
"key": "betright",
"name": "BetRight"
}
]
}
],
"meta": {
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary",
"note": "All feeds listed here are sourced directly by KrokOdds. Display-permitted in your own product with attribution; not licensed for resale as a standalone feed — see Terms 6.1. Aggregator-licensed odds are never served from /v1/odds-feed."
}
}GET/api/v1/odds-feed/clvFree tier
Historical closing-line odds (last snapshot per bookmaker before commence_time) from the CLV archive.
Backed by `clv_archive` table; Supabase-first with a Firestore fallback on query failure. `bookmaker` filter is applied in-memory AFTER the cache read so all sport/date combos share one cache entry. Cache 300s. `from` is clamped via clampHistoricalFrom to the caller's plan window. 400 on malformed from/to (must be YYYY-MM-DD).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | – | Filter by sport key, e.g. 'basketball_nba', 'aussierules_afl'. |
| bookmaker | string | No | – | Filter by bookmaker key, e.g. 'sportsbet', 'tab'. |
| from | string (YYYY-MM-DD) | No | – | Start of commence_time date range. Clamped to the caller's historicalDays window. |
| to | string (YYYY-MM-DD) | No | – | End of commence_time date range. |
| limit | integer | No | 50 (free) / 500 (api) | Max results; capped per tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/clv?sport=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "nrl-panthers-storm-20260903",
"sport": "rugbyleague_nrl",
"sport_title": "NRL",
"home_team": "Penrith Panthers",
"away_team": "Melbourne Storm",
"commence_time": "2026-09-03T09:30:00Z",
"archived_at": "2026-09-03T09:29:10Z",
"bookmakers": [
{
"key": "sportsbet",
"markets": [
{
"key": "h2h",
"outcomes": [
{
"name": "Penrith Panthers",
"bet_link": "https://www.sportsbet.com.au/..."
},
{
"name": "Melbourne Storm",
"bet_link": "https://www.sportsbet.com.au/..."
}
]
}
]
}
]
}
],
"meta": {
"sport": "rugbyleague_nrl",
"bookmaker": null,
"date_from": "2026-08-27",
"date_to": null,
"limit": 50,
"requested_limit": 50,
"total_matched": 1,
"truncated": false,
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/odds-feed/prediction-marketsFree tier
Prediction-market odds from Polymarket and Kalshi with de-vigged probabilities and CLOB order-book enrichment.
Reads `external_prediction_markets` (Supabase), synced every 30 min by `predictionMarketSync` Cloud Function. Cache 600s — matches the sync interval, fresher would be wasted reads. 400 on invalid source/type enum values.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| source | string enum | No | both | 'polymarket' or 'kalshi'. |
| sport | string | No | – | KrokOdds sport key, e.g. 'basketball_nba'. |
| type | string enum | No | – | 'game' or 'futures'. |
| limit | integer | No | 200 | Max 1000. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/prediction-markets?source=both"{
"success": true,
"data": [
{
"id": "pm_0x8f21a",
"source": "polymarket",
"type": "game",
"marketTitle": "Will the Sydney Kings win vs Melbourne United?",
"sportKey": "basketball_nbl",
"eventKey": "nbl-kings-united-20260903",
"matchConfidence": 0.94,
"impliedProbs": {
"yes": 0.57,
"no": 0.43
},
"rawPrices": {
"yes": 0.57,
"no": 0.43
},
"volume": 18450.25,
"liquidity": 6200.5,
"bestBid": 0.56,
"bestAsk": 0.58,
"spread": 0.02,
"lastTradePrice": 0.57,
"oneDayPriceChange": 0.03,
"updatedAt": "2026-09-03T08:15:00Z"
}
],
"meta": {
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary",
"sources": [
"polymarket"
],
"filter": {
"source": null,
"sport": "basketball_nbl",
"type": null
},
"note": "Prediction-market contract prices de-vigged and enriched with CLOB order-book data. Synced every 30 minutes."
}
}GET/api/v1/odds-feed/racingFree tier
Racing meetings -> races -> runners -> per-book fixed/tote odds, normalised across all proprietary racing feeds.
Gated on 'racing' not 'scraped_odds' by design — both are free today; kept separate so racing can move to paid independently. Per-collection scan cap is 600 (TAB alone runs ~420 meetings on a busy day). TAB stores thoroughbred as 'R' internally; API normalises to 'T' in both query and output. date before the caller's historicalDays window returns 403. Cache 300s.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | today (Australia/Melbourne) | Meeting date, AU-local. |
| bookmaker | string | No | – | Canonical key or white-label alias (e.g. 'swiftbet' resolves to bluebet). |
| venue | string | No | – | Substring match on venue slug. |
| race_type | string enum | No | – | T/R (thoroughbred), H (harness), G (greyhound). |
| limit | integer | No | 10 (free) / 500 (api) | Capped per tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/racing?date=today%20(Australia%2FMelbourne)"{
"success": true,
"data": [
{
"bookmaker_key": "tab",
"venue_slug": "flemington",
"race_type": "T",
"races": [
{
"race_number": 4,
"runners": [
{
"number": 7,
"name": "Golden Streak",
"win": 4.2,
"place": 1.65,
"bet_link": "https://tab.com.au/..."
}
]
}
]
}
],
"meta": {
"date": "2026-09-03",
"bookmaker": "tab",
"brand": null,
"limit": 10,
"requested_limit": 10,
"total_matched": 1,
"truncated": false,
"scan_capped_bookmakers": [],
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/odds-feed/racing/historyAPI plan
Historical per-book, per-runner racing odds snapshots (~5-min granularity) from the BigQuery cold archive — the backtest feed.
Tier detail: api (paid)
PAID-ONLY endpoint: gated on `bulk_export`, which is NOT in FREE_TIER_FEATURES — requires the api (paid) plan. `from` is REQUIRED (400 if missing/malformed); unbounded scans are rejected. Max 31-day window (400 if exceeded). Cursor-based pagination — supports `?fields=` projection via parseFields/projectRows. 503 (not 500) if the BQ table doesn't exist yet (mirror/flatten not live). TAB's 'R' race_type is normalised to 'T' in output; both 'T' and 'R' accepted as query aliases.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| from | string (YYYY-MM-DD) | Yes | – | Required. Start of date window; clamped to the caller's historicalDays. |
| to | string (YYYY-MM-DD) | No | same as from | End of date window. Must be >= from. Window capped at 31 days total. |
| bookmaker | string enum | No | – | One of tab, betfair, sportsbet, ladbrokes, neds, pointsbet, bluebet, palmerbet. |
| venue | string | No | – | Substring match on venue_slug. |
| race_type | string enum | No | – | T/R (thoroughbred), H (harness), G (greyhound). T and R both map to stored 'T'. |
| limit | integer | No | 5000 | Capped at 5000 for both free and api internal tiers. |
| cursor | string (base64url) | No | – | Opaque pagination cursor returned as meta.next_cursor from the previous page. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/racing/history?from=2026-09-01"{
"success": true,
"data": [
{
"date": "2026-09-03",
"snapshot_ts": "2026-09-03T05:05:00.000Z",
"snapshot_ms": 1772773500000,
"venue_slug": "flemington",
"race_no": 4,
"race_id": "flemington-20260903-r4",
"meeting_id": "flemington-20260903",
"race_type": "T",
"runner_no": 7,
"runner_name": "Golden Streak",
"scratched": false,
"book_key": "sportsbet",
"bet_link": "https://www.sportsbet.com.au/...",
"win": 4.2,
"place": 1.65,
"tote_win": 4.4,
"tote_place": 1.7,
"lay": 4.4
}
],
"meta": {
"from": "2026-08-20",
"to": "2026-09-03",
"partition_days": 15,
"bookmaker": "sportsbet",
"venue": null,
"race_type": "T",
"limit": 5000,
"requested_limit": 5000,
"next_cursor": "eyJtcyI6MTc3Mjc3MzUwMDAwMCwicmFjZSI6ImZsZW1pbmd0b24tMjAyNjA5MDMtcjQiLCJydW5uZXIiOjcsImJvb2siOiJzcG9ydHNiZXQifQ",
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/odds-feed/racing/movementsFree tier
Cross-bookmaker price movements (opening vs current) for today's/tomorrow's races, per runner.
Reads a single precomputed doc `racing_opportunities/racing_book_movements` (Supabase-first, Firestore fallback). Cache 60s. Returns empty array (not error) when the doc has no movements yet. Same tier gate as /api/v1/odds-feed/racing ('racing'), independent of 'scraped_odds'.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| venue | string | No | – | Substring match on venue name (case-insensitive). |
| race_type | string enum | No | – | T/R (thoroughbred), H (harness), G (greyhound). |
| bookmaker | string | No | – | Filter each runner's bookMovements down to one book. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/racing/movements?venue=Randwick"{
"success": true,
"data": [
{
"raceId": "flemington-20260903-r4",
"raceName": "Race 4",
"venue": "Flemington",
"raceNumber": 4,
"raceType": "T",
"jumpTime": "2026-09-03T05:10:00Z",
"runners": [
{
"number": 7,
"name": "Golden Streak",
"bestMovementPct": -12.5,
"bookMovements": [
{
"book": "sportsbet",
"opening": 4.8,
"current": 4.2,
"pct": -12.5,
"bet_link": "https://www.sportsbet.com.au/..."
},
{
"book": "tab",
"opening": 5,
"current": 4.4,
"pct": -12,
"bet_link": "https://tab.com.au/..."
}
]
}
]
}
],
"meta": {
"updatedAt": "2026-09-03T05:00:12Z"
}
}GET/api/v1/odds-feed/resultsFree tier
Settled race results (winner + full finishing order) from Krok Odds result feeds.
Backed by `race_results` collection (Supabase, single jsonb-path equality filter on date + limit). date before the caller's historicalDays window returns 403. Cache 300s, scan cap 1000 rows/day.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | No | today (Australia/Melbourne) | AU-local race date. |
| sport | string enum | No | – | R (thoroughbred), H (harness), G (greyhound); T accepted as alias for R. |
| source | string | No | – | Filter by result source. |
| venue | string | No | – | Substring match on venue slug. |
| limit | integer | No | 20 (free) / 800 (api) | Capped per tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/results?date=today%20(Australia%2FMelbourne)"{
"success": true,
"data": [
{
"date": "2026-09-03",
"venue": "Flemington",
"venueSlug": "flemington",
"raceNumber": 4,
"raceType": "R",
"source": "tab",
"status": "settled",
"winner": {
"number": 7,
"name": "Golden Streak"
},
"placings": [
{
"position": 1,
"number": 7,
"name": "Golden Streak"
},
{
"position": 2,
"number": 3,
"name": "Silver Line"
}
]
}
],
"meta": {
"date": "2026-09-03",
"sport": null,
"source": null,
"limit": 20,
"requested_limit": 20,
"total_matched": 1,
"truncated": false,
"scan_capped": false,
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/odds-feed/results/{date}Free tier
All settled race results for a specific AU-local date (path-param variant of /odds-feed/results).
Same source, tier gate and shape as /odds-feed/results (query-param version); date comes from the path instead of ?date=. 400 on malformed date; shares the `shared.ts` loader (`race_results` collection, cache 300s, scan cap 1000).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| date | string (YYYY-MM-DD) | Yes | – | AU-local race date. |
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string enum | No | – | R (thoroughbred), H (harness), G (greyhound). T accepted as alias for R. |
| source | string | No | – | Filter by result source. |
| venue | string | No | – | Substring match on venue slug. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/results/2026-09-01?sport=aussierules_afl"{
"success": true,
"data": [
{
"date": "2026-09-03",
"venue": "Flemington",
"venueSlug": "flemington",
"raceNumber": 4,
"raceType": "R",
"source": "tab",
"status": "settled",
"winner": {
"number": 7,
"name": "Golden Streak"
},
"placings": [
{
"position": 1,
"number": 7,
"name": "Golden Streak"
},
{
"position": 2,
"number": 3,
"name": "Silver Line"
}
],
"scratchings": [
5
],
"resultAt": "2026-09-03T05:14:22Z"
}
],
"meta": {
"date": "2026-09-03",
"sport": "R",
"source": null,
"limit": 20,
"total_matched": 1,
"truncated": false,
"scan_capped": false,
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/odds-feed/sportsFree tier
Sports with live proprietary event coverage, with per-sport live event counts and supplying bookmakers.
Counts come from PostgREST head-only COUNT queries, never a full document scan (deliberate cost guard, see June 2026 cost incident). Cache 1800s — sport list moves slowly. If ALL count queries fail, the route throws (500) rather than caching an empty list for 1800s. Racing-only books (BlueBet, Palmerbet) return empty sports list — that's real coverage, not an error; surfaced via meta.bookmaker_sports_feed = 'pending'.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| bookmaker | string | No | – | Canonical key or white-label alias; filters the (sport, book) counts in-memory over one cache entry. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/sports?bookmaker=sportsbet"{
"success": true,
"data": [
{
"sport": "aussierules_afl",
"event_count": 9,
"bookmakers": [
"betr",
"ladbrokes",
"pointsbet",
"sportsbet",
"tab"
],
"href": "/api/v1/odds-feed/sports/aussierules_afl"
},
{
"sport": "rugbyleague_nrl",
"event_count": 8,
"bookmakers": [
"ladbrokes",
"sportsbet",
"tab"
],
"href": "/api/v1/odds-feed/sports/rugbyleague_nrl"
}
],
"meta": {
"bookmaker": null,
"brand": null,
"bookmaker_sports_feed": null,
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/odds-feed/sports/{sport}Free tier
Events + full market/selection odds for one sport, fanned out across every book that scrapes it.
Per-collection scan cap 100, independent of the caller's `limit`, so an in-memory bookmaker filter still has a full candidate pool. 60s in-memory per-instance cache layered under the 300s unstable_cache to dedupe burst traffic. bet_link attached AFTER slicing to `limit`, outside the cache, so only served selections pay the link-build cost.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | Yes | – | Sport slug, e.g. 'basketball_nba', 'soccer_epl'. Resolved via resolveSportSlug; 400 if unresolvable. |
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| bookmaker | string | No | – | Canonical key or white-label alias. |
| league | string | No | – | League-specific slug, e.g. 'soccer_epl'; takes precedence over `competition`. |
| competition | string | No | – | Substring match on competition name (used only if `league` not set). |
| upcoming | boolean ('true') | No | false | Only events with start_time in the future. |
| markets | boolean ('false' to disable) | No | true | Set to 'false' to omit markets/selections from the response (event metadata only). |
| limit | integer | No | 25 (free) / 1000 (api) | Capped per tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/sports/aussierules_afl?bookmaker=sportsbet"{
"success": true,
"data": [
{
"bookmaker_key": "sportsbet",
"sport_slug": "soccer_epl",
"event": "Arsenal vs Chelsea",
"start_time": "2026-09-03T19:30:00Z",
"competition": "English Premier League",
"league_slug": "soccer_epl",
"markets": [
{
"key": "h2h",
"selections": [
{
"name": "Arsenal",
"price": 2.1,
"bet_link": "https://www.sportsbet.com.au/..."
},
{
"name": "Draw",
"price": 3.4,
"bet_link": "https://www.sportsbet.com.au/..."
},
{
"name": "Chelsea",
"price": 3.5,
"bet_link": "https://www.sportsbet.com.au/..."
}
]
}
]
}
],
"meta": {
"sport": "soccer_epl",
"bookmaker": null,
"brand": null,
"limit": 25,
"requested_limit": 25,
"total_matched": 1,
"truncated": false,
"scan_capped_bookmakers": [],
"direct_scrape": true,
"redistributable": true,
"license": "krokodds-proprietary"
}
}GET/api/v1/scraped-odds/bookmakers→ alias of /api/v1/odds-feed/bookmakersFree tier
Alias of /api/v1/odds-feed/bookmakers.
Directly re-exports { GET, OPTIONS } from ../../odds-feed/bookmakers/route — same handler, not a redirect. Historically was a 308 redirect but NextResponse.redirect() built the Location from internal request.url (localhost on Cloud Run/App Hosting), breaking external clients — now serves the handler directly.
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/scraped-odds/bookmakers"GET/api/v1/scraped-odds/racing→ alias of /api/v1/odds-feed/racingFree tier
Alias of /api/v1/odds-feed/racing.
Directly re-exports { GET, OPTIONS } from ../../odds-feed/racing/route — same handler, not a redirect.
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/scraped-odds/racing"GET/api/v1/scraped-odds/sports→ alias of /api/v1/odds-feed/sportsFree tier
Alias of /api/v1/odds-feed/sports.
Directly re-exports { GET, OPTIONS } from ../../odds-feed/sports/route — same handler, not a redirect.
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/scraped-odds/sports"GET/api/v1/scraped-odds/sports/{sport}→ alias of /api/v1/odds-feed/sports/{sport}Free tier
Alias of /api/v1/odds-feed/sports/{sport}.
Directly re-exports { GET, OPTIONS } from ../../../odds-feed/sports/[sport]/route — same handler, not a redirect.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | Yes | – | Passes through unchanged to the odds-feed handler. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/scraped-odds/sports/aussierules_afl"Sports
7 endpointsSport reference data, team form guides, league standings, injury reports and weather-impact analysis.
GET/api/v1/sportsFree tier
List all supported sports/leagues with category and AU coverage level.
No feature-gate/tierAllowsFeature check at all — only API-key auth + rate limit, no credit-cost debit path (verifyApiKey called without a cost arg). Available to any valid tier. `category` filter is normalised (case/space/dash/underscore insensitive) before matching. `refresh_interval_seconds` is a static heuristic (600 for keys ending `_winner`/`_preseason`, else 60) — not measured.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| category | string | No | – | Filter by sport category, case/punctuation-insensitive (e.g. "AFL", "au_sports", "Soccer", "Tennis"). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports?category=value"{
"success": true,
"data": [
{
"key": "aussierules_afl",
"label": "AFL",
"category": "AU Sports",
"au_coverage": "high",
"refresh_interval_seconds": 60
},
{
"key": "rugbyleague_nrl",
"label": "NRL",
"category": "AU Sports",
"au_coverage": "high",
"refresh_interval_seconds": 60
},
{
"key": "basketball_nba",
"label": "NBA",
"category": "Basketball",
"au_coverage": "high",
"refresh_interval_seconds": 60
},
{
"key": "tennis_atp_madrid_open",
"label": "Madrid Open (ATP)",
"category": "Tennis",
"au_coverage": "medium",
"refresh_interval_seconds": 60
},
{
"key": "soccer_epl",
"label": "Premier League",
"category": "Soccer",
"au_coverage": "high",
"refresh_interval_seconds": 60
}
],
"meta": {
"total": 5,
"categories": [
"AU Sports",
"Basketball",
"Tennis",
"Soccer"
],
"timestamp": "2026-09-03T04:00:00.000Z"
}
}GET/api/v1/sports/futuresFree tier
AFL futures markets — Brownlow Medal (12 markets) + Premiership Winner — aggregated across all AU bookmaker futures collections.
Reads from all external_*_futures collections (12 books). Filters for AFL-related futures using event/competition/sport name matching. Returns one row per (market, bookmaker) pair. Markets include: Brownlow Winner, Brownlow Without Daicos, Top 3/5/10/20, Leader After Round 6/10/15, Most 3-Vote Games, Most Poll Games, Most Votes Last 8 Rounds, and Premiership Winner. Cached 600s via unstable_cache + in-memory dedup.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| market | string | No | – | Filter by market key (e.g. 'brownlow_winner', 'premiership_winner'). |
| category | string | No | – | Filter by category: 'brownlow' or 'premiership'. |
| bookmaker | string | No | – | Filter to a single bookmaker (e.g. 'sportsbet', 'ladbrokes'). |
| limit | number | No | 200 | Max market entries returned. Clamped to 200 (free) / 1000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports/futures?market=h2h"{
"success": true,
"data": [
{
"market_key": "brownlow_winner",
"market_label": "Brownlow Medal Winner",
"category": "brownlow",
"bookmaker": "Sportsbet",
"bookmaker_key": "sportsbet",
"selections": [
{
"name": "Nick Daicos",
"odds": 3.5
},
{
"name": "Marcus Bontempelli",
"odds": 8
}
],
"captured_at": "2026-09-06T10:00:00.000Z"
}
],
"meta": {
"count": 1,
"sport": "aussie-rules",
"category": null,
"market": null,
"bookmaker": null,
"limit": 200,
"requested_limit": 200,
"total_matched": 1,
"truncated": false,
"direct_scrape": true,
"license": "krokodds-proprietary",
"timestamp": "2026-09-06T10:00:00.000Z"
}
}GET/api/v1/sports/formFree tier
Form deep-dive combining recent results, head-to-head, venue form and last-5/last-10 trend stats for teams, players, and horses.
Fans out across 5 Supabase sources (player_historical_stats, racing_runner_stats, team_game_log, game_results, player_props_results) and dedupes by synthetic id. `player`/`venue` filters only apply to the racing (horse) branch; `team` narrows sport-scoped sources. Cached 600s via unstable_cache, floored by v1Revalidate (min 300s).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Sport to analyse, e.g. "aussierules_afl", "rugbyleague_nrl", or "horse_racing". Case-insensitive. |
| team | string | No | – | Team name filter, partial/substring match. |
| player | string | No | – | Player or horse runner name filter, partial match. |
| venue | string | No | – | Venue filter, partial match — racing branch only. |
| limit | integer | No | 50 | Max results, clamped 1-200 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports/form?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "team_game_log_aussierules_afl_geelong",
"entity_type": "team",
"entity_name": "geelong",
"sport_key": "aussierules_afl",
"recent_results": [
"W",
"W",
"L",
"W",
"W"
],
"win_rate": 0.8,
"streak": "W2",
"venue_form": null,
"h2h_record": null,
"last_5": {
"games": 5,
"wins": 4,
"win_pct": 0.8,
"points_for_avg": 92.4,
"points_against_avg": 71.2,
"margin_avg": 21.2
},
"last_10": {
"games": 10,
"wins": 7,
"win_pct": 0.7,
"points_for_avg": 89.1,
"points_against_avg": 76.8,
"margin_avg": 12.3
},
"notes": "Strong recent form. On a W2 winning streak",
"updated_at": ""
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"filter": {
"sport_key": "aussierules_afl",
"team": "geelong",
"player": null,
"venue": null
},
"note": "Form deep-dive from game_results, player_props_results, and team logs — last 5/10 game averages, win rates, streaks, and score trends.",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/sports/injuriesFree tier
Cross-sport injury report — unified schema across all supported sports, sourced from injury_reports.
Reads from injury_reports (Supabase). Comment header says it 'extends /v1/injuries (racing-focused)' with multi-sport coverage and status_severity classification. `sport_key` also accepts a legacy `sport` alias param. Cached 300s.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport (AFL, NRL, NFL, NBA, NHL, MLB, etc). Alias: `sport`. |
| team | string | No | – | Filter by team name, partial match. |
| status | string | No | – | Filter by raw injury status string (e.g. "out", "doubtful", "questionable", "probable"), exact match case-insensitive. |
| limit | integer | No | 100 | Max results, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports/injuries?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "inj_nba_2026090301",
"sport_key": "basketball_nba",
"player_name": "Jaylen Brown",
"player_slug": "jaylen-brown",
"team": "Boston Celtics",
"status": "Questionable",
"status_severity": "questionable",
"reason": "Ankle soreness",
"body_part": "Ankle",
"date": "2026-09-03",
"season": 2026,
"source": "automated",
"updated_at": "2026-09-03T02:15:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"filter": {
"sport_key": "basketball_nba",
"team": null,
"status": null
},
"sport_breakdown": {
"basketball_nba": 1
},
"note": "Cross-sport injury aggregation from multiple sources.",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/sports/standingsFree tier
NHL and MLB league standings in a unified schema.
Covers NHL and MLB standings. Cached 3600s. `points` is null for MLB rows, `games_behind`/`elimination_number` null for NHL rows (fields are sport-specific but unified into one shape).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | both (nhl+mlb) | "nhl" or "mlb"; omit for both, lowercased. |
| limit | integer | No | 100 | Max rows, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports/standings?sport=both%20(nhl%2Bmlb)"{
"success": true,
"data": [
{
"sport": "MLB",
"team_id": "147",
"team": "New York Yankees",
"division": "AL East",
"league": "American League",
"group": null,
"wins": 84,
"losses": 58,
"points": null,
"win_pct": 0.592,
"games_behind": "-",
"streak": "W3",
"home": "45-25",
"away": "39-33",
"last_ten": "7-3",
"rank": 1,
"runs_scored": 712,
"runs_allowed": 601,
"run_differential": 111,
"clinched": false,
"elimination_number": null,
"updated_at": "2026-09-03T01:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"sport": "mlb",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/sports/weather-impactFree tier
Weather impact analysis for upcoming events — correlates Open-Meteo forecasts / racing-meeting weather with tagged impact notes (wind, rain, heat, cold, humidity, storm risk).
Two sources merged: external_openmeteo_forecasts (multi-sport outdoor events, skips retractable-roof venues) and racing_meetings (embedded BOM/Open-Meteo weather). `impact_tags` are rule-based thresholds (wind>30km/h, rain>5mm, temp>38C or <5C, humidity>80%, summary contains storm/thunder). Cached 300s.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport, e.g. "AFL", "NRL", "horse_racing". |
| venue | string | No | – | Venue filter, partial match. |
| date | string | No | – | Event date filter, YYYY-MM-DD, exact match. |
| limit | integer | No | 50 | Max results, clamped 1-200 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports/weather-impact?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "openmeteo_rugbyleague_nrl_accorstadium_20260906",
"sport_key": "NRL",
"venue": "Accor Stadium",
"date": "2026-09-06",
"race_type": null,
"track_condition": null,
"weather_summary": "Showers clearing, gusty southerly",
"temp_c": 16,
"humidity_pct": 78,
"rain_24h_mm": 6.2,
"wind_kmh": 34,
"wind_dir": null,
"impact_tags": [
"high_wind",
"heavy_rain"
],
"impact_notes": "Strong wind (34 km/h) favours inside barriers and wind-assisted runners. Significant rain (6.2mm) expected — prefer on-pace runners and firm-track specialists",
"updated_at": "2026-09-03T03:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"filter": {
"sport_key": "NRL",
"venue": null,
"date": null
},
"impact_summary": {
"high_wind": 1,
"heavy_rain": 1
},
"note": "Weather impact analysis from Open-Meteo forecasts and BOM/racing meeting data — temperature, wind, rain, humidity correlated with event conditions.",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/standingsFree tier
Unified standings for ALL sports from match_standings table, plus NHL/MLB from dedicated standings tables.
Reads from match_standings (all sports) plus NHL and MLB standings tables. Results are merged into a unified shape. Cached 3600s.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | – | Sport_key filter (e.g. "aussierules_afl", "rugbyleague_nrl"). Also accepts "nhl" or "mlb" for legacy tables. |
| limit | number | No | 100 | Max rows, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/standings?sport=aussierules_afl"{
"success": true,
"data": [
{
"sport_key": "aussierules_afl",
"tournament_id": "1",
"season_id": "2026",
"team_name": "Collingwood",
"team_id": "1",
"position": 1,
"played": 22,
"won": 18,
"drawn": 1,
"lost": 3,
"points_for": 1800,
"points_against": 1200,
"points_diff": 600,
"points": 73,
"updated_at": "2026-09-03T01:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"sport": "all"
}
}Tips & Predictions
4 endpointsAI-generated betting tips with confidence scores and historical accuracy tracking.
GET/api/v1/predictions→ alias of /api/v1/tipsFree tier
Deprecated alias of /api/v1/tips.
QUIRK: the file's own doc-comment claims it "proxies to tips with a sport_key=soccer filter for backwards compatibility", but the actual code is a bare re-export (`export { GET, OPTIONS } from '../tips/route'`) — NO soccer filter is applied; it is byte-identical behaviour to /api/v1/tips, all sports included. Comment is stale/misleading. Marked deprecated 2026-08-06, "will be removed in a future version" (no removal date set as of 2026-09-03). Same params/response/tier as /api/v1/tips.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Same as /api/v1/tips — filters by sport key despite the doc-comment implying a fixed soccer filter (that filter does not actually exist in code). |
| min_confidence | integer | No | 1 | Same as /api/v1/tips. |
| limit | integer | No | 50 | Same as /api/v1/tips. |
| include_settled | boolean | No | false | Same as /api/v1/tips. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/predictions?sport_key=aussierules_afl""identical shape to /api/v1/tips — see that entry"GET/api/v1/predictions/confidenceFree tier
Model confidence breakdown across racing, sports, and player props, plus 30-day historical accuracy by confidence bucket.
Gates on the 'tips' feature (tierAllowsFeature(tier,'tips')) even though the path is under /predictions. `force-dynamic`/`revalidate=0` — NOT cached via unstable_cache (response header still claims `Cache-Control: public, max-age=300` which is misleading given no server cache). Sports and props predictions are ONLY loaded if `sport`/`event_id` params are respectively provided — with type=all and neither param, only racing predictions populate. `summary.breakdown` is always `{}` in the current code (allBreakdowns is declared but never populated — dead aggregation). historicalAccuracy tries Supabase settled_tips first, falls back to Firestore.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | No | all | "racing" | "sports" | "props" | "all". Lowercased. |
| sport | string | No | – | Sport key — required for the sports branch to run (ignored if type excludes sports). |
| date | string | No | today (AU) | YYYY-MM-DD — racing branch only. |
| event_id | string | No | – | Specific event id — required for the props branch to run. |
| limit | integer | No | 50 | Max predictions per type-branch; capped 20 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/predictions/confidence?type=all"{
"success": true,
"meta": {
"type": "racing",
"sport": null,
"date": "2026-09-03",
"eventId": null,
"generatedAt": "2026-09-03T04:00:00.000Z",
"cacheTtl": 300
},
"summary": {
"overallConfidence": 0.74,
"breakdown": {},
"dataSources": [
"racing_form",
"sectionals",
"barrier_trial",
"weather"
],
"historicalAccuracy": {
"totalPredictions": 812,
"correctPredictions": 471,
"accuracyRate": 0.58,
"byConfidenceBucket": {
"high": {
"total": 210,
"correct": 148,
"rate": 0.7
},
"medium": {
"total": 380,
"correct": 218,
"rate": 0.57
},
"low": {
"total": 222,
"correct": 105,
"rate": 0.47
}
}
}
},
"predictions": [
{
"id": "racing-Written By-R6",
"type": "racing",
"confidence": 0.81,
"dataSources": [
"racing_form",
"sectionals",
"barrier_trial"
],
"breakdown": {
"weather": 0.6,
"barrier": 0.75,
"sectionals": 0.88,
"pedigree": 0.55,
"ensemble": 0.81
},
"reasoning": "Strong recent sectionals and barrier draw at Flemington R6."
}
]
}GET/api/v1/tipsFree tier
In-house model tips/predictions across all covered sports (active by default, or settled history with include_settled=true).
Uses the OLDER hand-rolled auth preamble (not guardV1Request/makeV1Route) — no explicit credit-cost debit in this file, verifyApiKey called with default cost. Merges canonical tips_rounds engine picks over legacy gameday_tips (canonical wins on conflicting fixtures; legacy only fills 7 leagues the engine doesn't cover) — active-tips path only, not for include_settled=true which reads settled_tips directly. Free tier cache 120s, api tier 30s (both floored to 300s by v1Revalidate). Free tier capped at 50 rows/request, api tier 1000.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key. Alias: `sport`. Lowercased. |
| min_confidence | integer | No | 1 | Minimum confidence 1-5, clamped. |
| limit | integer | No | 50 | Max rows; capped 50 (free) / 1000 (api) by getMaxLimit(). |
| include_settled | boolean | No | false | "true" to return settled_tips history (with resolution/actual_winner) instead of pending active tips. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tips?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "aussierules_afl-geelong-carlton-20260906t0740",
"event_id": "e_afl_20260906_gee_car",
"sport_key": "aussierules_afl",
"sport": "AFL",
"home_team": "Geelong Cats",
"away_team": "Carlton Blues",
"commence_time": "2026-09-06T07:40:00.000Z",
"pick": "Geelong Cats -12.5",
"pick_side": "home",
"confidence": 4,
"confidence_label": "High",
"consensus_prob_home": 0.71,
"consensus_prob_away": 0.29,
"predicted_margin": 18.4,
"predicted_margin_side": "home",
"resolution": "pending",
"actual_winner": null
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"include_settled": false,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/tips/accuracyFree tier
Historical hit-rate / track-record for the tips model, overall and broken down by confidence bucket, optionally scoped to one sport.
Reads model_track_record. Per-sport lookup filters on the `sport_key` column (indexed); the unscoped (all-sport) query has no column for `total` (lives in JSONB) so it sorts in memory after fetching up to `scanSize` (min(limit,100)) rows — an all-sports call is NOT a true global ranking beyond that scan window. Same free/api cache tiers as /tips (600s/120s, floored to 300s).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter to one sport. Alias: `sport`. Lowercased. |
| limit | integer | No | 50 | Max rows; capped 50 (free) / 1000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tips/accuracy?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"sport_key": "rugbyleague_nrl",
"wins": 142,
"losses": 88,
"pushes": 3,
"total": 233,
"decided": 230,
"hit_rate": 0.6174,
"by_confidence": [
{
"confidence": "3",
"wins": 51,
"losses": 44,
"pushes": 1,
"total": 96,
"hit_rate": 0.5368
},
{
"confidence": "4",
"wins": 63,
"losses": 32,
"pushes": 1,
"total": 96,
"hit_rate": 0.6632
},
{
"confidence": "5",
"wins": 28,
"losses": 12,
"pushes": 1,
"total": 41,
"hit_rate": 0.7
}
],
"last_settled_at": "2026-09-02T22:30:00.000Z",
"updated_at": "2026-09-02T22:31:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "rugbyleague_nrl",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}Tennis
5 endpointsATP/WTA rankings, upcoming fixtures, match results, Elo ratings and surface-specific stats.
GET/api/v1/tennis/eloFree tier
Overall or surface-specific Elo ratings for ATP/WTA players (UTS/tennisabstract-derived).
Two very differently-shaped response rows depending on `surface`: with `surface` set it reads external_utstat_surface and returns {id, player_name, player_id, surface, elo}; without it, reads external_utstat_elo and returns the richer {rank, player_name, country_code, elo_rating, best_rank, best_rating, points_diff} shape — field names differ (elo vs elo_rating) between the two modes. `player` filter fetches up to limit*6 rows first then filters in memory (partial match).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| player | string | No | – | Player name filter, partial/substring, case-insensitive. |
| surface | string | No | – | "hard" | "clay" | "grass" — switches to the surface-Elo table/shape. Any other value is ignored (falls back to overall Elo). |
| limit | integer | No | 100 | Max rows, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tennis/elo?player=lebron-james"{
"success": true,
"data": [
{
"id": "atp_sinner_j",
"rank": 1,
"player_name": "Jannik Sinner",
"player_id": "s0ag",
"country_code": "ITA",
"elo_rating": 2312,
"best_rank": 1,
"best_rating": 2340,
"points_diff": 45
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"player": null,
"surface": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/tennis/fixturesAPI plan
Upcoming/scheduled tennis fixtures by tour.
Tier detail: api (paid) — 'historical' is NOT in FREE_TIER_FEATURES
Gated on the 'historical' feature despite being a forward-looking fixtures list (not a historical archive) — free-tier keys get a 402 tier-gate response here. creditCost=2 (archive-weighted, per the makeV1Route cfg comment: standard 1 / archive 5 / bulk 25 — this is priced above standard but not full archive). Built via makeV1Route/tennis_fixtures collection.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| tour | string | No | – | Tour filter, e.g. "ATP" or "WTA" — exact match on the `tour` JSONB field. |
| limit | integer | No | 100 | Max rows; capped 50 (free) / 200 (api) per limitByTier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tennis/fixtures?tour=value"{
"success": true,
"data": [
{
"tour": "ATP",
"tournament": "US Open",
"round": "QF",
"player1": "Carlos Alcaraz",
"player2": "Alexander Zverev",
"date": "2026-09-04",
"surface": "hard",
"venue": "USTA Billie Jean King National Tennis Center"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 100,
"requested_limit": 100,
"tour": "ATP",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 998,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/tennis/matchesFree tier
Historical ATP/WTA singles match results (Jeff Sackmann dataset), filterable by season/tour/surface.
Reads external_sackmann_tennis_matches. Only `season` is an indexed equality filter pushed to Supabase; `tour`/`surface` are applied in-memory after over-fetching (limit*6) when set — per the file's own query-design comment this keeps it on an indexed column. Throws V1BadRequest (400) if `season` is non-numeric.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| season | integer | No | – | Season year, e.g. 2026. Must parse as a number or 400 V1BadRequest is returned. |
| tour | string | No | – | "atp" or "wta", case-insensitive, in-memory filter. |
| surface | string | No | – | "hard" | "clay" | "grass", case-insensitive, in-memory filter. |
| limit | integer | No | 100 | Max rows; capped 100 (free) / 500 (api) per limitByTier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tennis/matches?season=10"{
"success": true,
"data": [
{
"season": 2026,
"tour": "atp",
"surface": "hard",
"tournament": "US Open",
"round": "SF",
"winner_name": "Jannik Sinner",
"loser_name": "Novak Djokovic",
"score": "6-4 3-6 7-6(5) 6-2",
"date": "2026-09-05"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"season": 2026,
"tour": "atp",
"surface": "hard",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/tennis/rankingsAPI plan
Current ATP/WTA player rankings.
Tier detail: api (paid) — 'historical' is NOT in FREE_TIER_FEATURES
Gated on 'historical' despite being a current-state rankings list, not an archive — free-tier keys get 402. No filter params at all beyond `limit` (no tour/player filter exposed). creditCost=2 like /tennis/fixtures.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| limit | integer | No | 100 | Max rows; capped 100 (free) / 500 (api) per limitByTier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tennis/rankings?limit=100"{
"success": true,
"data": [
{
"ranking": 1,
"tour": "ATP",
"player_name": "Jannik Sinner",
"country": "ITA",
"points": 11330,
"movement": 0
},
{
"ranking": 1,
"tour": "WTA",
"player_name": "Iga Swiatek",
"country": "POL",
"points": 9945,
"movement": 1
}
],
"meta": {
"count": 2,
"tier": "api",
"limit": 100,
"requested_limit": 100,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 998,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/tennis/surface-statsFree tier
Tennis surface-specific Elo/win-rate stats (hard/clay/grass) for ATP/WTA players.
Reads the SAME table (external_utstat_surface) and returns the SAME shape ({id, player_name, player_id, surface, elo}) as /api/v1/tennis/elo?surface=X — effectively a duplicate/alias of that mode with a dedicated path. No win-rate field despite the doc-comment and summary saying "win rates and Elo by surface" — only `elo` is actually returned, no win_pct/matches_played field exists in code.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| player | string | No | – | Player name filter, partial/substring, case-insensitive. |
| surface | string | No | – | "hard" | "clay" | "grass" — invalid values are silently ignored (no filter applied). |
| limit | integer | No | 100 | Max rows, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tennis/surface-stats?player=lebron-james"{
"success": true,
"data": [
{
"id": "atp_alcaraz_c_clay",
"player_name": "Carlos Alcaraz",
"player_id": "a0e2",
"surface": "clay",
"elo": 2280
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"player": null,
"surface": "clay",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 949,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}Historical
6 endpointsDeep archives — player and team game logs, shot charts, AFL/NRL historical data. Paid API plan only.
GET/api/v1/historical/aflAPI plan
AFL historical archive — player game logs (2010+) or match results (2018+ via Akareen, pre-2018 via AFL Tables backfill).
Tier detail: api (paid) — 'historical' is NOT in FREE_TIER_FEATURES
Cache: free=3600s, api=1800s. kind=match with year<2018 silently swaps collection to the historical matches collection (identically shaped). Uses shared makeV1Route wrapper (historical-window clamp on since/from, empty-result credit refund, 204 on empty).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| kind | string | No | player | 'player' or 'match'. 400 if any other value. |
| year | integer | No | – | Season year filter. Values <2018 with kind=match route to the AFL Tables backfill collection instead of Akareen. |
| round | string | No | – | Round filter, only applied when kind=player (post-fetch filter, over-fetches limit*4 rows). |
| limit | integer | No | 100 | Row cap. Tier-capped: free=100, api=500. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/afl?apikey / api_key=value"{
"success": true,
"data": [
{
"id": "afl_2026_r22_j_daicos_collingwood",
"player": "Josh Daicos",
"team": "Collingwood",
"year": "2026",
"round": "22",
"disposals": 28,
"goals": 1,
"kicks": 19,
"handballs": 9,
"marks": 6,
"tackles": 4,
"fixtureId": 923841
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 500,
"requested_limit": 100,
"kind": "player",
"year": 2026,
"round": "22",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 8421,
"reset": "2026-10-01"
}
}
}GET/api/v1/historical/nrlAPI plan
NRL historical archive — player/match data from 2010 onwards.
Tier detail: api (paid)
Cache: free=3600s, api=1800s. Three distinct underlying Supabase collections keyed off 'kind', each with its own season field name (competitionYear vs season).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| kind | string | No | modern | 'player' (2010-2023 archive), 'match' (2010-2023 archive), or 'modern' (2024+ current data). 400 on any other value. |
| season / year | integer | No | – | Season filter; 'season' checked first, falls back to 'year'. |
| limit | integer | No | 100 | Tier-capped: free=100, api=500. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/nrl?apikey / api_key=value"{
"success": true,
"data": [
{
"id": "nrl_2026_r24_n_hynes_sharks",
"player": "Nicho Hynes",
"team": "Cronulla Sharks",
"season": "2026",
"tries": 1,
"tackles": 22,
"runMetres": 118,
"tackleBreaks": 3,
"fixtureId": 448120
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 500,
"requested_limit": 100,
"kind": "modern",
"season": 2026,
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 8420,
"reset": "2026-10-01"
}
}
}GET/api/v1/historical/player-game-logAPI plan
Full accumulated game-by-game history for a player+stat combo (not a fixed recent-form window — depth varies per player).
Tier detail: api (paid)
Does NOT use makeV1Route — hand-rolled handler, no explicit creditCost field/mechanism (unlike the shared wrapper's credit-cost headers). recentGames written append-only via Firestore arrayUnion so a doc holds FULL history, not a 15-game window; history_from/history_to expose real span. Cache: free=600s, api=120s. Over-fetches limit*4 (capped 2000) then filters player/team in memory.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key / sport | string | Yes | – | e.g. basketball_nba, americanfootball_nfl. 400 if missing. |
| player | string | No | – | Case-insensitive substring match, filtered post-fetch. |
| team | string | No | – | Case-insensitive substring match, filtered post-fetch. |
| stat_key / stat | string | No | – | Exact-match stat key filter (e.g. 'points', 'disposals'). |
| since | string (YYYY-MM-DD) | No | – | Filters recentGames[] to dates >= since. Malformed values ignored. Clamped forward by clampHistoricalFrom() to the key's historicalDays window. |
| limit | integer | No | 100 | Tier-capped: free=50, api=2000. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/player-game-log?sport_key / sport=aussierules_afl&apikey / api_key=value"{
"success": true,
"data": [
{
"id": "basketball_nba__nikola_jokic__points",
"sport_key": "basketball_nba",
"player": "Nikola Jokic",
"team": "Denver Nuggets",
"stat_key": "points",
"games_count": 3,
"history_to": "2026-04-12",
"history_from": "2025-10-22",
"recent_games": [
{
"date": "2026-04-12",
"opponent": "Los Angeles Lakers",
"is_home": true,
"stat_value": 31,
"fixture_id": 501233
},
{
"date": "2026-04-09",
"opponent": "Phoenix Suns",
"is_home": false,
"stat_value": 27,
"fixture_id": 501198
},
{
"date": "2026-04-06",
"opponent": "Golden State Warriors",
"is_home": true,
"stat_value": 24,
"fixture_id": 501167
}
],
"updated_at": "2026-04-13T02:11:04.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 2000,
"requested_limit": 100,
"sport_key": "basketball_nba",
"stat_key": "points",
"since": "2025-10-22",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 8419,
"reset": "2026-10-01"
}
}
}GET/api/v1/historical/player-statsAPI plan
Season averages, splits, and market lines for a player from the historical player-stats archive; single-slug lookup or sport-wide/team-filtered listing.
Tier detail: api (paid)
Cache: free=3600s, api=1800s. Has BOTH loadSupabase and loadBq (BQ fallback only fires if BQ_ENABLED and Supabase throws). Slug lookup bypasses the limit param entirely.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key / sport | string | Yes | – | 400 'sport_key parameter required' if missing. |
| slug | string | No | – | Canonical player slug. If present, does a direct id lookup on `${sportKey}__${slug}` (limit ignored, returns 0 or 1 row). |
| team | string | No | – | Case-insensitive exact match on team, filtered post-fetch (only applies when slug absent). |
| limit | integer | No | 100 | Tier-capped: free=100, api=500. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/player-stats?sport_key / sport=aussierules_afl&apikey / api_key=value"{
"success": true,
"data": [
{
"id": "basketball_nba__jayson_tatum",
"name": "Jayson Tatum",
"slug": "jayson_tatum",
"sport_key": "basketball_nba",
"team": "Boston Celtics",
"position": "SF",
"active": true,
"coverage": {
"seasons": [
"2023",
"2024",
"2025",
"2026"
]
},
"season_averages": {
"points": 26.8,
"rebounds": 8.1,
"assists": 4.6
},
"splits": {
"home": {
"points": 27.9
},
"away": {
"points": 25.7
}
},
"market_lines": {
"points": 26.5,
"rebounds": 8
},
"updated_at": "2026-09-02T22:04:11.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 500,
"requested_limit": 100,
"sport_key": "basketball_nba",
"slug": "jayson_tatum",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 8418,
"reset": "2026-10-01"
}
}
}GET/api/v1/historical/shot-chartAPI plan
NBA shot chart — half-court shot coordinates for a player, optionally filtered to one season.
Tier detail: api (paid)
Cache: free=3600s, api=1800s. One doc per (season, player) in player_shot_charts; data.shots is the shot-point array. NBA-only (Wave 3 feature).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| slug / player | string | Yes | – | Canonical player slug (spaces normalized to underscores, lowercased). 400 'slug is required' if missing. |
| season | string | No | – | Exact-match season filter, applied in-memory over an over-fetched (limit*4) window. |
| limit | integer | No | 100 | Tier-capped: free=20, api=60 (much lower cap than other historical routes). |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/shot-chart?slug / player=lebron-james&apikey / api_key=value"{
"success": true,
"data": [
{
"id": "2026__nikola_jokic",
"playerSlug": "nikola_jokic",
"season": "2026",
"team": "Denver Nuggets",
"shots": [
{
"x": 12.4,
"y": 8.1,
"made": true,
"value": 2,
"distance_ft": 14,
"date": "2026-04-12"
},
{
"x": -21,
"y": 22.6,
"made": false,
"value": 3,
"distance_ft": 27,
"date": "2026-04-12"
}
],
"updated_at": "2026-04-13T01:50:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 60,
"requested_limit": 100,
"slug": "nikola_jokic",
"season": "2026",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 60,
"remaining": 8417,
"reset": "2026-10-01"
}
}
}GET/api/v1/historical/team-game-logAPI plan
Historical team-level game results (scores, periods, status) with date-range filtering.
Tier detail: api (paid)
Does NOT use makeV1Route (hand-rolled handler, mirrors historical/player-game-log structure) — no explicit per-request creditCost field. Cache: free=600s, api=120s. Ordered DESC by date server-side (unlike most sibling historical routes which have no explicit order).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key / sport | string | Yes | – | 400 if missing. |
| team | string | No | – | Case-insensitive substring match against home OR away team, filtered post-fetch. |
| since | string (YYYY-MM-DD) | No | – | Range start. Clamped forward by clampHistoricalFrom() to the key's historicalDays window; malformed values ignored. |
| until | string (YYYY-MM-DD) | No | – | Range end, validated YYYY-MM-DD only (not clamped). |
| limit | integer | No | 100 | Tier-capped: free=100, api=5000 (highest cap of all historical routes). |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/team-game-log?sport_key / sport=aussierules_afl&apikey / api_key=value"{
"success": true,
"data": [
{
"id": "americanfootball_nfl_2026_wk1_kc_bal",
"sport_key": "americanfootball_nfl",
"fixture_id": 700421,
"date": "2026-09-05",
"home_team": "Baltimore Ravens",
"away_team": "Kansas City Chiefs",
"home_score": 24,
"away_score": 27,
"periods": {
"q1": 7,
"q2": 3,
"q3": 7,
"q4": 7
},
"league": "NFL",
"league_id": 1,
"country": "USA",
"status": "final",
"updated_at": "2026-09-06T03:10:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 5000,
"requested_limit": 100,
"sport_key": "americanfootball_nfl",
"since": "2025-09-01",
"until": null,
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 8416,
"reset": "2026-10-01"
}
}
}Reference
6 endpointsStatic lookup data for building your own UI — players, venues, headshots, team logos and sport/league metadata.
GET/api/v1/reference/headshotsFree tier
Static player headshot photo URLs by sport, optionally a single player by slug.
Tier detail: free — 'reference' IS in FREE_TIER_FEATURES
Cache: free=86400s, api=43200s (24h/12h, longest TTLs of any v1 route — static reference data). No explicit creditCost set in config → defaults to 1 (makeV1Route default).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | Yes | – | 400 'sport_key parameter required' if missing. |
| slug | string | No | – | Single-player lookup via doc id `${sportKey}_${slug}` (bypasses limit). |
| limit | integer | No | 100 | Tier-capped: free=200, api=1000. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/reference/headshots?sport_key=aussierules_afl&apikey / api_key=value"{
"success": true,
"data": [
{
"id": "basketball_nba_jayson_tatum",
"name": "Jayson Tatum",
"slug": "jayson_tatum",
"sport_key": "basketball_nba",
"photo_url": "https://a.espncdn.com/i/headshots/nba/players/full/4066261.png"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 200,
"requested_limit": 100,
"sport_key": "basketball_nba",
"slug": "jayson_tatum",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 998,
"reset": "2026-10-01"
}
}
}GET/api/v1/reference/metadataAPI plan
Team metadata (name, league, venue, etc.) from the team_metadata collection, optionally filtered by sport.
Tier detail: api (paid) — QUIRK: despite living under /reference/, this route gates on the 'historical' feature, NOT 'reference', so it is paid-tier-only unlike its sibling reference/* endpoints
Cache: free=86400s, api=43200s. Results ordered ASC by $.name. Row shape is `r.data ?? r` — raw stored doc data passed through largely unmapped (no field renaming like other routes).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | – | Exact-match sport filter. |
| limit | integer | No | 100 | Tier-capped: free=50, api=200. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/reference/metadata?apikey / api_key=value"{
"success": true,
"data": [
{
"name": "Boston Celtics",
"sport": "basketball_nba",
"league": "NBA",
"conference": "Eastern",
"division": "Atlantic",
"venue": "TD Garden",
"city": "Boston",
"country": "USA",
"abbreviation": "BOS"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 200,
"requested_limit": 100,
"sport": "basketball_nba",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 8415,
"reset": "2026-10-01"
}
}
}GET/api/v1/reference/playersFree tier
Player metadata lookup by name and/or sport from the player_metadata collection.
Cache: free=86400s, api=43200s. Ordered DESC by $.fetchedAt (most recently updated first). Row shape passthrough (`r.data ?? r`).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | No | – | Exact-match player name filter. |
| sport | string | No | – | Exact-match sport filter. |
| limit | integer | No | 100 | Tier-capped: free=50, api=200. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/reference/players?apikey / api_key=value"{
"success": true,
"data": [
{
"name": "Jayson Tatum",
"sport": "basketball_nba",
"team": "Boston Celtics",
"position": "SF",
"jersey_number": 0,
"height": "6'8\"",
"weight": "210 lbs",
"birth_date": "1998-03-03",
"fetchedAt": "2026-09-01T10:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 100,
"name": "Jayson Tatum",
"sport": "basketball_nba",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 997,
"reset": "2026-10-01"
}
}
}GET/api/v1/reference/team-logosFree tier
Static team logo URLs and name aliases by sport, backfilled from team_metadata when the primary logo source is sparse.
Cache: free=86400s, api=43200s. When primary external_team_logos rows < limit, best-effort fills gaps from team_metadata (logoUrl/badgeUrl fields), deduping by team name; fallback failures are swallowed silently. No explicit creditCost → defaults to 1.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | Yes | – | 400 'sport_key parameter required' if missing. |
| limit | integer | No | 100 | Tier-capped: free=200, api=1000. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/reference/team-logos?sport_key=aussierules_afl&apikey / api_key=value"{
"success": true,
"data": [
{
"id": "basketball_nba_bos",
"names": [
"Boston Celtics",
"Celtics",
"BOS"
],
"logo": "https://a.espncdn.com/i/teamlogos/nba/500/bos.png",
"sport_key": "basketball_nba",
"external_id": "bos"
},
{
"id": "team_metadata_gsw",
"names": [
"Golden State Warriors"
],
"logo": "https://cdn.krokodds.com.au/logos/nba/gsw.png",
"sport_key": "basketball_nba",
"external_id": null,
"source": "team_metadata"
}
],
"meta": {
"count": 2,
"tier": "free",
"limit": 200,
"requested_limit": 100,
"sport_key": "basketball_nba",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 996,
"reset": "2026-10-01"
}
}
}GET/api/v1/reference/venuesFree tier
Venue metadata (location, capacity, surface, etc.) lookup by name and/or sport.
Cache: free=86400s, api=43200s. Ordered DESC by $.fetchedAt. Row shape passthrough (`r.data ?? r`).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| name | string | No | – | Exact-match venue name filter. |
| sport | string | No | – | Exact-match sport filter. |
| limit | integer | No | 100 | Tier-capped: free=50, api=200. |
| apikey / api_key | string | Yes | – | API key, alternatively via X-API-Key header. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/reference/venues?apikey / api_key=value"{
"success": true,
"data": [
{
"name": "Marvel Stadium",
"sport": "australianfootball_afl",
"city": "Melbourne",
"state": "VIC",
"country": "Australia",
"capacity": 53359,
"surface": "grass",
"roof": "retractable",
"fetchedAt": "2026-08-15T06:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 100,
"name": "Marvel Stadium",
"sport": "australianfootball_afl",
"timestamp": "2026-09-03T04:12:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 995,
"reset": "2026-10-01"
}
}
}GET/api/v1/rosterFree tier
Team roster/squad data from team_roster table. Currently supports AFL teams; extensible to all sports.
Reads from team_roster table (Supabase). team_id and sport are Supabase-level filters; team name is a post-filter. Sorted by jersey number ascending. Cached 86400s (24h).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| team_id | number | No | – | SofaScore team ID (e.g. 4452 for Fremantle). |
| team | string | No | – | Team name filter (partial match, case-insensitive). |
| sport | string | No | – | Sport_key filter (e.g. "aussierules_afl"). |
| limit | number | No | 100 | Max rows, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/roster?team_id=abc123"{
"success": true,
"data": [
{
"sport_key": "aussierules_afl",
"team_id": 4452,
"team_name": "Fremantle",
"season_id": null,
"player_id": "p001",
"player_name": "Andrew Brayshaw",
"position": "Midfielder",
"jersey_number": 8,
"nationality": "AUS",
"age": 25,
"updated_at": "2026-09-01T00:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"sport": "aussierules_afl"
}
}Prediction Markets
3 endpointsPolymarket and Kalshi sentiment plus a universal prediction-market catalogue and resolutions feed.
GET/api/v1/prediction-markets/sentimentFree tier
Directional sentiment (bullish/bearish/neutral) derived from Polymarket + Kalshi consensus pricing, plus a heuristic market-disagreement flag.
Tier detail: free — 'prediction_markets' IS in FREE_TIER_FEATURES
Cache: 300s flat (no per-tier split). Only status='open' rows queried. direction: bullish if consensus>0.6, bearish if <0.4, else neutral. disagreement: bid-ask spread>0.15 OR (2+ outcome prices, top two within 0.1 of each other) — reads as inverted/odd logic, worth flagging in docs. Uses guardV1Request/v1Success helper, not makeV1Route.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | ILIKE substring match against the market question text (not a structured sport field), e.g. 'NFL', 'AFL', 'EPL'. |
| category | string | No | – | Exact match on platform_category. |
| source | string | No | both | 'polymarket' or 'kalshi'. 400 on any other value. |
| limit | integer | No | 50 | Clamped 1-200 (MAX_LIMIT=200). |
| offset | integer | No | 0 | Pagination offset. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/prediction-markets/sentiment?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "pm_will-chiefs-win-super-bowl-lx",
"source": "polymarket",
"question": "Will the Kansas City Chiefs win Super Bowl LX?",
"category": "sports",
"consensus_probability": 0.183,
"volume": 842000,
"liquidity": 61500,
"price_direction": "bearish",
"market_disagreement": false,
"status": "open",
"end_date": "2027-02-14",
"fetched_at": "2026-09-03T03:45:00.000Z"
}
],
"meta": {
"count": 1,
"license": "krokodds-derived",
"filter": {
"sport_key": "NFL",
"category": null,
"source": null
},
"sentiment_summary": {
"bearish": 1
},
"note": "Prediction market sentiment derived from Polymarket + Kalshi pricing. Direction is heuristic from consensus probability.",
"limit": 50
}
}GET/api/v1/prediction-markets/universalFree tier
Full-catalog Polymarket + Kalshi markets across EVERY category (weather, politics, crypto, economics, sports) — not just sports-matched markets. Synced every 30 min by universalMarketSync Cloud Function.
Cache: 300s flat. Distinct from /api/v1/odds-feed/prediction-markets, which serves the sports-matched subset (external_prediction_markets table) — this route is the raw full catalog (universal_prediction_markets table), not sport-filtered server-side (only via question-text search).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| source | string | No | – | 'polymarket' or 'kalshi'. 400 on invalid value. |
| category | string | No | – | Exact match on platform_category. |
| status | string | No | open | One of open|closed|settled|halted. 400 on invalid value. |
| limit | integer | No | 50 | Clamped 1-200. |
| offset | integer | No | 0 | Pagination offset. |
| search | string | No | – | Case-insensitive ILIKE match on question text. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/prediction-markets/universal?source=value"{
"success": true,
"data": [
{
"id": "pm_us-fed-rate-cut-september-2026",
"source": "kalshi",
"platform_category": "economics",
"market_id": "FED-RATE-SEP26",
"question": "Will the Fed cut rates at the September 2026 FOMC meeting?",
"description": "Resolves YES if the FOMC announces a rate cut at its September 2026 meeting.",
"outcomes": [
"Yes",
"No"
],
"outcome_prices": [
0.71,
0.29
],
"status": "open",
"result": null,
"volume": 1250000,
"liquidity": 98000,
"best_bid": 0.7,
"best_ask": 0.72,
"end_date": "2026-09-18",
"created_at": "2026-07-01T00:00:00.000Z",
"fetched_at": "2026-09-03T03:30:00.000Z",
"resolved_at": null
}
],
"meta": {
"count": 1,
"license": "krokodds-proprietary",
"filter": {
"source": null,
"category": "economics",
"status": "open",
"search": null
},
"pagination": {
"limit": 50,
"offset": 0
},
"note": "Full-catalog Polymarket + Kalshi markets across every category. Synced every 30 minutes.",
"limit": 50
}
}GET/api/v1/prediction-markets/universal/resolutionsFree tier
Append-only archive of RESOLVED Polymarket + Kalshi markets (final outcome + prices at resolution), with a day-based lookback window.
Cache: 300s flat. Separate `market_resolutions` table (distinct from universal_prediction_markets) — append-only, never mutated after resolution. No explicit historical-days tier clamp applied to `days` param (unlike historical/* routes' since/from clamp) — same 30/365-day window available to free and api tiers alike.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| source | string | No | – | 'polymarket' or 'kalshi'. 400 on invalid value. |
| category | string | No | – | Exact match on platform_category. |
| days | integer | No | 30 | Lookback window in days for resolved_at, clamped 1-365. |
| limit | integer | No | 50 | Clamped 1-200. |
| offset | integer | No | 0 | Pagination offset. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/prediction-markets/universal/resolutions?source=value"{
"success": true,
"data": [
{
"id": "pm_us-fed-rate-cut-july-2026",
"source": "kalshi",
"market_id": "FED-RATE-JUL26",
"question": "Will the Fed cut rates at the July 2026 FOMC meeting?",
"platform_category": "economics",
"outcomes": [
"Yes",
"No"
],
"result": "No",
"final_prices": [
0.05,
0.95
],
"volume_at_resolution": 980000,
"resolved_at": "2026-07-30T18:00:00.000Z",
"captured_at": "2026-07-30T18:05:00.000Z"
}
],
"meta": {
"count": 1,
"license": "krokodds-proprietary",
"filter": {
"source": "kalshi",
"category": "economics",
"days": 30
},
"pagination": {
"limit": 50,
"offset": 0
},
"note": "Resolved Polymarket + Kalshi markets, append-only resolution archive.",
"limit": 50
}
}Other Sports
20 endpointsCricket, cycling, esports, Formula 1, golf, MMA, boxing, NFL and soccer coverage beyond the AU staples.
GET/api/v1/boxing/fight-resultsFree tier
Boxer career fight-results ledger (record, KO count, full fight list) by boxer slug or the most recently updated boxers.
Gated on the 'gameday' feature (not 'multisport') even though it's a static career record, not a live gameday signal — likely a copy/paste from another handler. No BigQuery fallback; hand-rolled auth preamble (predates makeV1Route helper) so it has no explicit creditCost/refund-on-empty logic.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. Also accepted as ?apikey= or ?api_key=. |
| boxer_slug | string | No | – | Exact boxer slug; when set, does a direct single-document lookup instead of a listing query and ignores since/limit ordering. |
| since | string (ISO date) | No | – | Only return boxers updated on/after this date. Clamped to the key's historical-window entitlement. |
| limit | integer | No | 50 | Max rows. Capped at 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/boxing/fight-results?X-API-Key=value"{
"success": true,
"data": [
{
"boxer_slug": "marcus-oyelaran",
"name": "Marcus Oyelaran",
"total_fights": 24,
"wins": 21,
"losses": 2,
"draws": 1,
"no_contests": 0,
"ko_wins": 15,
"fights": [
{
"opponent": "Diego Salazar",
"date": "2026-08-15",
"result": "W",
"method": "KO",
"round": 6
},
{
"opponent": "Kwame Boateng",
"date": "2026-05-02",
"result": "W",
"method": "UD",
"round": 12
}
],
"updated_at": "2026-09-02T11:14:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"boxer_slug": "marcus-oyelaran",
"since": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 942,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/cricket/inningsFree tier
Cricsheet ball-by-ball derived per-player batting/bowling innings lines.
Returns 400 'sport_key, match_type, or season parameter required' if none supplied. When only match_type or season is given, over-fetches limit*6 rows and filters client-side, so results can be sparse relative to the requested limit on a narrow slice.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| sport_key | string | Yes | – | Filter by internal sport/competition key. If set, this is the single indexed equality filter; match_type/season are applied in-memory afterward. |
| match_type | string | Yes | – | e.g. 'T20', 'ODI', 'Test'. Used as the indexed filter only when sport_key is absent. |
| season | string | Yes | – | Season label, e.g. '2026'. Stored/compared as a string, not a number. |
| limit | integer | No | 100 | Max rows. Capped at 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/cricket/innings?X-API-Key=value&sport_key=aussierules_afl&match_type=value"{
"success": true,
"data": [
{
"id": "bbl2026-m34-innings-rt-smith",
"sportKey": "cricket_big_bash",
"matchType": "T20",
"season": "2026",
"matchId": "bbl2026-m34",
"player": "R. Thomson-Smith",
"team": "Perth Scorchers",
"battingRuns": 62,
"battingBalls": 41,
"fours": 6,
"sixes": 3,
"wickets": 0,
"oversBowled": 0,
"runsConceded": 0,
"fetchedAt": "2026-09-02T13:05:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"sport_key": "cricket_big_bash",
"match_type": null,
"season": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 3,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/cricket/liveAPI plan
Recent/live cricket match snapshots (from the general cricket_matches archive, most-recently-fetched first).
Named 'live' but gated on 'historical' (paid-only) and reads a plain archive table sorted by fetchedAt DESC — no is-live/status filter server-side, so callers must filter status client-side. Free tier cannot access this endpoint at all.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| sportKey | string | No | – | Filter by sport/competition key. |
| limit | integer | No | 100 | Max rows. Capped at 20 (free) / 50 (api) — note the config cap is far below the parse default of 100. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/cricket/live?X-API-Key=value"{
"success": true,
"data": [
{
"id": "cm-2026-09-03-ind-vs-eng",
"sportKey": "cricket_test_match",
"status": "in_progress",
"homeTeam": "India",
"awayTeam": "England",
"venue": "Wankhede Stadium, Mumbai",
"session": "Day 3, Session 2",
"score": {
"home": "312/6",
"away": "not yet batted"
},
"fetchedAt": "2026-09-03T03:55:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"sportKey": "cricket_test_match",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1998,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/cycling/resultsAPI plan
Historical cycling race results (stage/GC placings).
Paid-only ('historical' feature). No date-range param — only exact race-name match; free tier gets a 402 regardless of params.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| race | string | No | – | Exact race name filter. |
| limit | integer | No | 100 | Max rows. Capped at 20 (free) / 50 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/cycling/results?X-API-Key=value"{
"success": true,
"data": [
{
"race": "Vuelta a Espana",
"stage": 18,
"date": "2026-09-02",
"rider": "Tobias Halvorsen",
"team": "Visma | Lease a Bike",
"position": 1,
"time": "4:12:08",
"gc_position": 2,
"fetchedAt": "2026-09-02T18:40:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 20,
"requested_limit": 100,
"race": "Vuelta a Espana",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 20,
"remaining": 1997,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/cycling/ridersAPI plan
Cycling rider profiles/roster records.
Both name and team filters are exact-match equality, no partial/fuzzy search.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| name | string | No | – | Exact rider name filter. |
| team | string | No | – | Exact team name filter. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/cycling/riders?X-API-Key=value"{
"success": true,
"data": [
{
"name": "Tobias Halvorsen",
"team": "Visma | Lease a Bike",
"nationality": "Norway",
"dob": "1999-04-11",
"specialty": "Climber",
"fetchedAt": "2026-08-30T09:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"name": null,
"team": "Visma | Lease a Bike",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1996,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/cycling/startlistsAPI plan
Cycling race startlists by race and date.
Both filters exact-match; no range query for date.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| race | string | No | – | Exact race name filter. |
| date | string (YYYY-MM-DD) | No | – | Exact date filter. |
| limit | integer | No | 100 | Max rows. Capped at 20 (free) / 50 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/cycling/startlists?X-API-Key=value"{
"success": true,
"data": [
{
"race": "Vuelta a Espana",
"date": "2026-09-03",
"stage": 19,
"riders": [
{
"name": "Tobias Halvorsen",
"team": "Visma | Lease a Bike",
"bib": 21
},
{
"name": "Elia Fontaine",
"team": "UAE Team Emirates",
"bib": 1
}
],
"fetchedAt": "2026-09-03T00:30:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 20,
"requested_limit": 100,
"race": "Vuelta a Espana",
"date": "2026-09-03",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 20,
"remaining": 1995,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/esports/leaguesAPI plan
Esports league/competition metadata.
Simple single-filter listing, no search-by-name.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| sportKey | string | No | – | Filter by sport/game key, e.g. 'esports_lol'. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/esports/leagues?X-API-Key=value"{
"success": true,
"data": [
{
"sportKey": "esports_lol",
"leagueId": "lck-2026-summer",
"name": "LCK 2026 Summer",
"region": "Korea",
"game": "League of Legends",
"season": "2026 Summer",
"fetchedAt": "2026-08-28T05:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"sportKey": "esports_lol",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1994,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/esports/matchesAPI plan
Esports match schedule/results, ordered by start time ascending.
Ordered by $.beginAt ASC (upcoming-first), unlike most sibling endpoints which order DESC by fetchedAt.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| sportKey | string | No | – | Filter by sport/game key. |
| status | string | No | – | Match status filter, e.g. 'not_started', 'running', 'finished'. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/esports/matches?X-API-Key=value"{
"success": true,
"data": [
{
"sportKey": "esports_csgo",
"matchId": "blast-premier-fall-2026-m12",
"status": "not_started",
"league": "BLAST Premier Fall Groups 2026",
"teamA": "Team Vitality",
"teamB": "Natus Vincere",
"beginAt": "2026-09-04T10:00:00.000Z",
"bestOf": 3,
"fetchedAt": "2026-09-03T02:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"sportKey": "esports_csgo",
"status": "not_started",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1993,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/esports/teamsAPI plan
Esports team profiles/roster metadata.
Same skeleton as esports/leagues, single equality filter on sportKey.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| sportKey | string | No | – | Filter by sport/game key. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/esports/teams?X-API-Key=value"{
"success": true,
"data": [
{
"sportKey": "esports_lol",
"teamId": "t1",
"name": "T1",
"region": "Korea",
"roster": [
"Zeus",
"Oner",
"Faker",
"Gumayusi",
"Keria"
],
"fetchedAt": "2026-08-25T06:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"sportKey": "esports_lol",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1992,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/f1/racesFree tier
Formula 1 race schedule/status (API-Sports feed) — season, circuit, weather, fastest lap.
Code comment flags a past bug fix: this was previously gated on 'racing' (AU racing tier) which wrongly locked F1 out; now correctly gated on 'multisport'. Distinct dataset from /v1/f1/results (this is the API-Sports schedule, not Ergast/Jolpica results).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| race_id | string | No | – | Exact race id; does a direct single-document lookup, ignoring season/since/limit. |
| season | integer | No | – | Season year, e.g. 2026. |
| since | string (ISO date) | No | – | Only races updated on/after this date. Clamped to the key's historical-window entitlement. |
| limit | integer | No | 50 | Max rows. Capped at 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/f1/races?X-API-Key=value"{
"success": true,
"data": [
{
"race_id": "f1-2026-r16-monza",
"season": 2026,
"competition": "Formula 1",
"circuit": "Autodromo Nazionale Monza",
"date": "2026-09-06T13:00:00.000Z",
"type": "Race",
"status": "Scheduled",
"laps": 53,
"distance": "306.72km",
"timezone": "Europe/Rome",
"weather": "Sunny, 26C",
"fastest_lap": null,
"updated_at": "2026-09-03T01:20:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"race_id": null,
"season": 2026,
"since": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 940,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/f1/resultsFree tier
Formula 1 driver race results per season/round (Ergast/Jolpica feed).
Separate source/collection from /v1/f1/races — do not conflate the two in docs. When 'round' is passed without narrowing further, over-fetches limit*6 rows to filter client-side, so results can undercount near the tail of large seasons.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| season | integer | No | – | Season year. Returns 400 if non-numeric. |
| round | integer | No | – | Race round number within the season. Returns 400 if non-numeric. Filtered in-memory after an over-fetch. |
| limit | integer | No | 100 | Max rows. Capped at 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/f1/results?X-API-Key=value"{
"success": true,
"data": [
{
"season": "2026",
"round": 16,
"raceName": "Italian Grand Prix",
"driver": "Lando Norris",
"constructor": "McLaren",
"grid": 1,
"position": 1,
"points": 25,
"status": "Finished",
"fastestLapTime": "1:21.046"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"season": 2026,
"round": 16,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 939,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/golf/rankingsAPI plan
Golf world/tour rankings by player.
No tour/date param — single collection covers all rankings snapshots, latest by fetchedAt.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| player | string | No | – | Exact player name filter. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/golf/rankings?X-API-Key=value"{
"success": true,
"data": [
{
"player": "Ludvig Karlberg",
"rank": 4,
"points": 8.42,
"events_played": 19,
"tour": "PGA Tour",
"fetchedAt": "2026-09-01T12:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"player": "Ludvig Karlberg",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1991,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/golf/skillsAPI plan
Golf strokes-gained / approach-shot skill ratings by player.
`source=approach` switches to a different underlying collection (golf_approach_skills) with an approach-shot-specific shape — document both variants.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| source | string | No | skills | Which dataset: 'skills' (golf_player_skills) or 'approach' (golf_approach_skills). 400 if any other value. |
| player | string | No | – | Exact player name filter. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/golf/skills?X-API-Key=value"{
"success": true,
"data": [
{
"player": "Ludvig Karlberg",
"sg_total": 2.14,
"sg_off_tee": 0.61,
"sg_approach": 0.88,
"sg_around_green": 0.34,
"sg_putting": 0.31,
"fetchedAt": "2026-08-30T09:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"source": "skills",
"player": "Ludvig Karlberg",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1990,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/golf/statsAPI plan
Golf player statistical projections (most recent snapshot).
No query filters at all besides limit — collection is 'golf_projections' despite the route name 'stats'; there is no player/event filter param.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/golf/stats?X-API-Key=value"{
"success": true,
"data": [
{
"player": "Ludvig Karlberg",
"event": "BMW Championship",
"projected_finish": 6,
"win_prob": 0.041,
"top10_prob": 0.32,
"made_cut_prob": 0.91,
"fetchedAt": "2026-08-29T15:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1989,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/leaderboards/soccerFree tier
Soccer league top-scorers or top-assists leaderboard for a league+season.
Data shape differs from every other endpoint here: `data` is a single object (not an array) containing a `players` array. Backing docs are chunked (>1MB Firestore doc limit split into `{docId}_c{i}` chunk docs) and reassembled server-side; Supabase path falls back to Firestore on any read error. If the doc doesn't exist, returns success:true with data:null and count:0 (not a 404).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| kind | string | No | topscorers | 'topscorers' or 'topassists'. Any other value returns 400. |
| league_id | string | Yes | – | League id, e.g. '39' (Premier League). Required — 400 if missing. |
| season | string | Yes | – | Season year, e.g. '2026'. Required — 400 if missing. |
| limit | integer | No | 100 | Max players returned. Capped at 100 (free) / 5000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/leaderboards/soccer?X-API-Key=value&league_id=abc123&season=value"{
"success": true,
"data": {
"league_id": "39",
"league": "Premier League",
"season": "2026",
"kind": "topscorers",
"players": [
{
"rank": 1,
"player": "Erling Haaland",
"team": "Manchester City",
"goals": 9,
"appearances": 4
},
{
"rank": 2,
"player": "Alexander Isak",
"team": "Liverpool",
"goals": 7,
"appearances": 4
}
],
"count": 2,
"updated_at": "2026-09-02T22:00:00.000Z"
},
"meta": {
"count": 2,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"kind": "topscorers",
"league_id": "39",
"season": "2026",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 946,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/mma/fightsFree tier
MMA fight card entries/results by fight id or recently-updated window.
Same hand-rolled auth preamble pattern as boxing/fight-results and gated on 'gameday' rather than 'multisport'.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| fight_id | string | No | – | Exact fight id; does a direct single-document lookup, ignoring since/limit. |
| since | string (ISO date) | No | – | Only fights updated on/after this date. Clamped to the key's historical-window entitlement. |
| limit | integer | No | 50 | Max rows. Capped at 50 (free) / 2000 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/mma/fights?X-API-Key=value"{
"success": true,
"data": [
{
"fight_id": "ufc-306-fontaine-vs-reyes",
"date": "2026-09-06",
"time": "22:00",
"slug": "ufc-306",
"status": "scheduled",
"is_main": true,
"category": "Welterweight",
"fighters": [
{
"name": "Diego Fontaine",
"record": "19-3-0"
},
{
"name": "Marcus Reyes",
"record": "17-2-0"
}
],
"league": "UFC",
"country": "USA",
"updated_at": "2026-09-02T20:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"fight_id": null,
"since": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 938,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/nfl/scheduleFree tier
NFL full-season schedule with teams, venues, kickoff times, and starting QBs.
The only endpoint in this batch built on the newer guardV1Request/v1Success helper (adds X-Krok-Data-Source: derived header and license:'krokodds-derived' in meta). Explicit creditCost=1 passed to guardV1Request. Sorted by gameday then gametime.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| season | integer | No | current calendar year | Season year. 400 if non-numeric when provided. |
| week | integer | No | – | Week number filter. 400 if non-numeric when provided. |
| team | string | No | – | Case-insensitive team name substring OR exact abbreviation match, checked against both home and away. |
| limit | integer | No | 100 | Max rows, 1-500. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/nfl/schedule?X-API-Key=value"{
"success": true,
"data": [
{
"id": "2026-w1-kc-bal",
"game_id": "2026090400",
"season": 2026,
"season_type": "REG",
"week": 1,
"gameday": "2026-09-04",
"weekday": "Thursday",
"gametime": "20:20",
"away_team": "Kansas City Chiefs",
"away_abbr": "KC",
"home_team": "Baltimore Ravens",
"home_abbr": "BAL",
"stadium": "M&T Bank Stadium",
"location": "Baltimore, MD",
"result": null,
"overtime": false,
"away_qb": "Patrick Mahomes",
"home_qb": "Lamar Jackson"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"season": 2026,
"week": 1,
"team": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 950,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/soccer/bundesligaAPI plan
OpenLigaDB Bundesliga / 2.Bundesliga / DFB-Pokal match results — fallback settlement source for German-football props.
creditCost=5 (archive-tier weight) despite the endpoint label being a plain fixtures list. `league` is mandatory. season/matchday applied in-memory after an 8x over-fetch when combined with league.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| league | string | Yes | – | One of 'bl1' (Bundesliga), 'bl2' (2. Bundesliga), 'dfb' (DFB-Pokal). Required — 400 if missing or invalid. |
| season | integer | No | – | Start-year of the season, e.g. 2025 for 2025/26. |
| matchday | integer | No | – | Matchday/round number within the season. |
| limit | integer | No | 100 | Max rows. Capped at 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/soccer/bundesliga?X-API-Key=value&league=value"{
"success": true,
"data": [
{
"matchId": 68321,
"date": "2026-08-30T13:30:00.000Z",
"matchday": "3. Spieltag",
"homeTeam": "Bayer 04 Leverkusen",
"awayTeam": "Borussia Dortmund",
"homeGoals": 2,
"awayGoals": 2,
"homeGoalsHT": 1,
"awayGoalsHT": 1,
"finished": true,
"location": "BayArena, Leverkusen"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 100,
"requested_limit": 100,
"league": "bl1",
"season": 2025,
"matchday": 3,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 1985,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/soccer/closing-oddsFree tier
football-data.co.uk settled soccer matches with Bet365 + Pinnacle opening AND closing prices (Asian handicap, O/U 2.5) — the CLV reference dataset.
creditCost=5 despite being on the free tier ('closing_lines' is a FREE_TIER_FEATURES member) — free-tier callers still burn 5 credits per call. 400 if neither league/league_code nor season is provided. from/to are plain string comparisons against a YYYY-MM-DD date field, not true date-range queries at the DB layer.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| league | string | Yes | – | League code (e.g. 'E0' for EPL). Also accepted as league_code. |
| league_code | string | Yes | – | Alias for league. |
| season | string | Yes | – | Season label, e.g. '2025-2026'. |
| from | string (YYYY-MM-DD) | No | – | Inclusive start date filter (in-memory, string comparison on date). |
| to | string (YYYY-MM-DD) | No | – | Inclusive end date filter (in-memory, string comparison on date). |
| limit | integer | No | 100 | Max rows. Capped at 100 (free) / 500 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/soccer/closing-odds?X-API-Key=value&league=value&league_code=value"{
"success": true,
"data": [
{
"id": "fdcouk-e0-2025-2026-020",
"date": "2026-08-30",
"time": "17:30",
"league_code": "E0",
"season": "2025-2026",
"home_team": "Arsenal",
"away_team": "Tottenham",
"ftr": "H",
"htr": "D",
"ft_home_goals": 2,
"ft_away_goals": 1,
"b365_open": {
"home": 1.65,
"draw": 4,
"away": 5.5
},
"b365_close": {
"home": 1.58,
"draw": 4.1,
"away": 6
},
"pinnacle_open": {
"home": 1.68,
"draw": 3.95,
"away": 5.3
},
"pinnacle_close": {
"home": 1.6,
"draw": 4.05,
"away": 5.8
},
"asian_handicap": {
"line": -0.75,
"home_close": 1.95,
"away_close": 1.97
},
"over_under_25": {
"over_close": 1.85,
"under_close": 2
}
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"league_code": "E0",
"season": null,
"from": "2026-08-01",
"to": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 944,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/soccer/fixturesAPI plan
Soccer fixtures/results by sport key and status, ordered by kickoff time ascending.
Ordered by $.utcDate ASC (upcoming-first), like esports/matches, unlike the DESC-by-fetchedAt convention used by most other collection routes.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| X-API-Key | string | Yes | – | API key. |
| sportKey | string | No | – | Filter by sport/competition key, e.g. 'soccer_epl'. |
| status | string | No | – | Match status filter, e.g. 'SCHEDULED', 'IN_PLAY', 'FINISHED'. |
| limit | integer | No | 100 | Max rows. Capped at 50 (free) / 200 (api). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/soccer/fixtures?X-API-Key=value"{
"success": true,
"data": [
{
"sportKey": "soccer_epl",
"matchId": "epl-2026-gw4-avl-che",
"status": "SCHEDULED",
"utcDate": "2026-09-06T14:00:00.000Z",
"homeTeam": "Aston Villa",
"awayTeam": "Chelsea",
"venue": "Villa Park",
"matchday": 4
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 100,
"sportKey": "soccer_epl",
"status": "SCHEDULED",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 1980,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}Infrastructure
14 endpointsAccount and billing endpoints, bulk export, SSE streaming, webhook management and API health.
GET/api/v1/advanced-statsFree tier
Multiplexed advanced/derived stat archives across 14 sources (MLB Statcast, NHL/MoneyPuck skaters+goalies, NCAAB, soccer xG/FBref, NCAAF, NFL NGS/snaps/depth, NRL, AFL, tennis closing odds), selected via ?source=.
Tier detail: free (advanced_stats is in FREE_TIER_FEATURES; both free and api tier keys can call it)
Built on the shared makeV1Route factory. Cache TTL: free=3600s, api=1800s. Falls back to BigQuery mirror only if loadSupabase throws AND a mirror exists; the soccer advanced stats source has no BQ mirror, so it reads from Supabase. Empty result set returns HTTP 204 and refunds the credit.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| source | string | Yes | – | One of: mlb_advanced, nhl_skaters, ncaab_schools, soccer_xg, ncaaf_player_games, nfl_ngs, nfl_snaps, nfl_depth, soccer_fbref, nrl_player_games, afl_matches, tennis_closing_odds, nhl_moneypuck_skaters, nhl_moneypuck_goalies. 400 V1BadRequest if not in this list. |
| season | integer | No | – | Season/year filter (field name varies per source: 'season' or 'year'). Non-numeric value returns 400. |
| team | playerType | league | sportKey | opponent | flavor | round | teamNickname | playerSlug | team1 | team2 | venue | tour | surface | position | situation | compId | squad | string | No | – | Per-source in-memory filter, only the subset listed in that source's `extra` array is honored (e.g. mlb_advanced accepts team/playerType; nhl_moneypuck_skaters accepts team/position/playerSlug/situation, defaulting situation to 'all' when omitted). |
| limit | integer | No | 100 | Row cap. Free tier max 100, api tier max 500. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/advanced-stats?source=value"{
"success": true,
"data": [
{
"id": "abc123",
"season": 2026,
"team": "NYY",
"playerType": "batter",
"xwOBA": 0.361,
"barrelPct": 9.8
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"source": "mlb_advanced",
"season": 2026,
"filters": {
"team": "nyy"
},
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GETPOST/api/v1/bets/trackFree tier
GET returns a user's tracked-bet history plus a computed P&L summary (by sport, by bookmaker, streaks, monthly breakdown); POST logs a new bet.
Tier detail: free (bet_tracking is in FREE_TIER_FEATURES)
Requires a valid API key with the bet_tracking feature (via guardV1Request) PLUS a Firebase-session-derived `x-user-id` request header — the API key alone is not sufficient, so this is a hybrid api_key+session auth route despite being under v1. Reads/writes the `bets` Supabase table directly (no Firestore fallback in this file). POST returns 201 with the created row and its plaintext id (no secret hidden). GET response cached 60s via unstable_cache, keyed per user+filters.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter bets by sport. |
| status | string | No | – | Filter by bet status: pending | won | lost | void. |
| from | string (YYYY-MM-DD) | No | – | Start date filter (created_at >= from). |
| to | string (YYYY-MM-DD) | No | – | End date filter (created_at <= to 23:59:59). |
| limit | integer | No | 50 | Max results, clamped 1-200. |
| offset | integer | No | 0 | Pagination offset. |
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| event_id | string | Yes | – | Event identifier. |
| sport_key | string | Yes | – | Sport key. |
| market | string | No | "" | Market type, e.g. h2h, spread, total. |
| selection | string | No | – | What was backed. |
| odds | number | No | – | Decimal odds. |
| stake | number | No | – | Stake amount (AUD). potential_return is auto-computed as odds*stake when both are present. |
| status | string | No | pending | pending | won | lost | void. |
| commence_time | string (ISO) | No | – | Event start time; passed through into the stored `data` jsonb blob (not a dedicated column). |
curl \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"event_id":"evt_abc123","sport_key":"aussierules_afl","market":"\"\"","selection":"value","odds":"10","stake":"10","status":"pending","commence_time":"value"}' \
"https://krokodds.com.au/api/v1/api/v1/bets/track?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "u1_1735689600_ab12cd",
"user_id": "u1",
"event_id": "evt123",
"sport_key": "basketball_nba",
"market": "h2h",
"selection": "Lakers",
"odds": 1.85,
"stake": 50,
"potential_return": 92.5,
"profit_loss": null,
"status": "pending",
"created_at": "2026-09-01T00:00:00.000Z"
}
],
"meta": {
"license": "krokodds-derived",
"summary": {
"total_bets": 12,
"pending": 2,
"won": 7,
"lost": 3,
"void": 0,
"total_stake": 600,
"total_return": 740,
"net_profit": 140,
"roi_pct": 23.33,
"win_rate": 70,
"by_sport": {
"basketball_nba": {
"bets": 5,
"stake": 250,
"profit": 60,
"roi": 24,
"win_rate": 66.67
}
},
"by_bookmaker": {
"sportsbet": {
"bets": 5,
"stake": 250,
"profit": 60,
"roi": 24,
"win_rate": 66.67
}
},
"streak": {
"current": 2,
"type": "win",
"best_win": 4,
"best_loss": 2
},
"monthly": {
"2026-08": {
"bets": 6,
"stake": 300,
"profit": 70,
"roi": 23.33
}
}
},
"pagination": {
"limit": 50,
"offset": 0
}
}
}GET/api/v1/bookmakersFree tier
Static-ish catalog of AU-facing bookmakers KrokOdds supports (id, name, provider, feed_group, is_aggregator, is_exchange, supported flag).
Tier detail: free (no tierAllowsFeature gate at all — any valid key of any tier can call it)
24h Cache-Control (private, max-age=86400, stale-while-revalidate=172800) — deliberately 'private' not 'public' since a shared/CDN cache would leak the auth-gated response to keyless callers (CDN keys on URL only, ignores X-API-Key). X-Krok-Version: 2 (higher than most other v1 routes, which are version 1).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| region | string | No | au | Region filter; only 'au' returns rows, anything else (non-empty) returns an empty array — forward-compat hook, AU is the only region currently supported. |
| provider | string | No | – | Filter by provider metadata (matches provider field or provider === 'both'). |
| include_aggregators | boolean ("true"/"false" string) | No | false | When true, also includes AGGREGATOR_FEEDS (deduped clone lines, no arb value) marked supported:false. |
| fields | string (comma-separated) | No | – | Field projection via parseFields/projectRows. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/bookmakers?region=au"{
"success": true,
"data": [
{
"id": "sportsbet",
"name": "Sportsbet",
"region": "au",
"odds_api_key": "sportsbet",
"supported": true,
"provider": "krok-odds",
"feed_group": "sportsbet",
"is_aggregator": false,
"is_exchange": false
}
],
"meta": {
"count": 42,
"tier": "free",
"region": "au",
"provider": "all",
"include_aggregators": false,
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": ""
}
}
}GET/api/v1/bulkAPI plan
Enterprise cursor-paginated JSON bulk export of the historical archive across 13 datasets (team/player game logs, prop results, game results, MLB/soccer/NCAAF/NFL/NHL/NRL advanced stats, Betfair BSP/results). No ?dataset= returns the dataset manifest.
Tier detail: api (bulk_export is paid-only, not in FREE_TIER_FEATURES)
creditCost: 25 (enterprise-weighted, set explicitly in makeV1Route config). Cache TTL 3600s both tiers (archive is immutable so cached hard). Cursor pagination via supabasePageById (`.order(id).gt(id, cursor).limit(n)`) — index-cheap and resumable. Companion CSV/JSON export at /api/v1/export shares the same bulk_export gate and 25-credit cost.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| dataset | string | No | – | One of: team_game_logs, player_game_logs, player_history, prop_results, game_results, mlb_advanced, soccer_xg, ncaaf_player_games, nfl_tracking, nhl_skaters, nrl_player_games, exchange_bsp, exchange_results. Omitted → returns manifest (list of {dataset, description}) with no DB read. |
| sport_key | sport | string | No | – | Only supported for player_game_logs and nrl_player_games datasets; 400 otherwise (message points caller to /v1/historical/player-game-log). |
| cursor | string | No | – | Opaque id cursor for resumable pagination — pass back `next_cursor` from the previous response until it's null. |
| limit | integer | No | 100 | Row cap. Free tier max 100, api tier max 500 (though the feature itself is api-tier gated). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/bulk?dataset=value"{
"success": true,
"data": [
{
"id": "0001",
"sportKey": "americanfootball_nfl",
"team": "KC",
"gameDate": "2026-08-30"
}
],
"meta": {
"count": 100,
"tier": "api",
"limit": 100,
"requested_limit": 100,
"dataset": "team_game_logs",
"collection": "team_game_log",
"next_cursor": "0100",
"has_more": true,
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 49975,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/closing-linesFree tier
Cross-event aggregator of opening vs closing H2H odds (for CLV computation at scale) — flips the single-event odds-history ladder inside out into many events with opens/close/movement.
Tier detail: free (closing_lines is in FREE_TIER_FEATURES)
Hand-rolled route (not makeV1Route) with its own dual-source fallback: primary `odds_history` table (has opening_odds), falls back to legacy `gameday_odds_history` shape if odds_history returns 0 rows or throws — the two sources use different field casing (commence_time vs commenceTime) which the route normalizes. Cache 300s free / 60s api. Distinct from the single-event ladder at /v1/odds-history?event_id=.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | sport | string | No | – | Filter by sport. |
| since | string (date/ISO) | No | – | Clamped by clampHistoricalFrom() against the key's historicalDays window. |
| until | string (date/ISO) | No | – | Upper bound on commence_time. |
| only_closed | boolean ("true"/"false") | No | true | When true, only events whose commence_time has already passed are returned. |
| limit | integer | No | 50 | Free tier max 50, api tier max 2000. |
| cursor | string (opaque) | No | – | Keyset cursor encoding {v: commence_time ISO, id}. |
| fields | string (comma-separated) | No | – | Field projection. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/closing-lines?sport_key | sport=aussierules_afl"{
"success": true,
"data": [
{
"id": "evt-1",
"event_id": "evt-1",
"sport_key": "basketball_nba",
"home_team": "Lakers",
"away_team": "Celtics",
"commence_time": "2026-09-02T00:00:00.000Z",
"is_closed": true,
"opens": {
"home": 1.9,
"away": 1.95,
"opened_at_ms": null,
"home_implied_prob": 0.5263,
"away_implied_prob": 0.5128
},
"close": {
"home": 1.83,
"away": 2.05,
"home_book": "sportsbet",
"away_book": "sportsbet",
"captured_at_ms": null,
"home_implied_prob": 0.5464,
"away_implied_prob": 0.4878
},
"movement": {
"home_delta": -0.07,
"away_delta": 0.1,
"home_direction": "shorten",
"away_direction": "drift"
},
"snapshot_count": 0,
"updated_at": "2026-09-02T05:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "basketball_nba",
"since": null,
"until": null,
"only_closed": true,
"next_cursor": null,
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 999,
"reset": ""
}
}
}GET/api/v1/clv-archive/{eventId}Free tier
Fetch the archived closing odds snapshot for a single event by path parameter.
Tier detail: any signed-in user (no tier/feature gate — this is NOT an API-key route despite living under /api/v1/)
This route authenticates with the dashboard's browser session, not an API key – it isn't callable from third-party integrations.
IMPORTANT DISCREPANCY: this route uses Firebase session auth (getServerUser()), returning 401 Unauthorized if no signed-in user — it does NOT use verifyApiKey / X-API-Key at all, unlike almost every other /v1/* route. Reads Supabase `clv_archive` table by doc id first (isSupabase('clv-archive') gate), falls back to Firestore `clv_archive` collection on Supabase error. 404 if the event isn't archived.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| eventId | string (path param) | Yes | – | Dynamic route segment [eventId]; trimmed, 400 if empty after trim. |
curl \
# dashboard-only: uses your browser session, not an API key
"https://krokodds.com.au/api/v1/api/v1/clv-archive/evt_abc123"{
"success": true,
"data": {
"event_id": "evt-1",
"sport": "basketball_nba",
"sport_title": "NBA",
"home_team": "Lakers",
"away_team": "Celtics",
"commence_time": "2026-09-02T00:00:00.000Z",
"bookmakers": [],
"archived_at": "2026-09-02T05:00:00.000Z",
"expire_at": null
}
}GET/api/v1/ev-hit-ratesFree tier
Historical hit-rate / calibration stats for +EV bets, aggregated by market category (total_bets, hit_count, hit_rate, avg_clv, avg_ev).
Tier detail: free (positive_ev is in FREE_TIER_FEATURES)
Hand-rolled route with a THREE-tier fallback chain: Supabase (ev_hit_rates table) -> point-get on Firestore evHitRates if category set -> BigQuery mirror -> full Firestore collection scan (capped at min(limit,500)). Cache TTL 3600s free / 600s api. Per memory note 'ev-records-empty' class issues have hit similar tables historically — verify live data before citing exact numbers in docs.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| category | string | No | – | Market category id — when set, does a point-get by doc id (category) instead of a scan. |
| limit | integer | No | 100 | Free tier max 100, api tier max 5000. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/ev-hit-rates?category=value"{
"success": true,
"data": [
{
"category": "h2h",
"total_bets": 542,
"hit_count": 301,
"hit_rate": 0.5554,
"avg_clv": 1.8,
"avg_ev": 4.2,
"updated_at": "2026-09-01T00:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"category": null,
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": ""
}
}
}GET/api/v1/exchangeFree tier
KrokOdds Exchange (Betfair) pricing suite multiplexed by ?source=: settled racing BSP, race results, sports match-odds, scorer props, and live pre-jump snapshots.
Tier detail: free (betfair_exchange is in FREE_TIER_FEATURES — filterExchangeForHobby in api-tier-gate.ts is now a no-op, exchange pricing is available on all tiers)
Built on makeV1Route, Supabase-only (no loadBq). Cache TTL 600s free / 300s api. Underlying writers are functions/src/external/betfair/*. Per CLAUDE.md, this exchange endpoint is distinct from the max-fidelity odds_archive/racing_odds_archive firehose tables (migration 056) which are internal-only, not exposed via this API.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| source | string | Yes | – | One of: bsp, results, match_odds, scorer_props, snapshots. 400 if not in this list. |
| raceKey | string | No | – | Racing filter for bsp/results, keyed `${venueSlug}_${raceNumber}`. |
| venueSlug | marketType | string | No | – | Additional bsp filters (in-memory, post-fetch). |
| sport | string | No | – | Equality filter (indexed path) for match_odds/scorer_props/snapshots sources. |
| eventId | string | No | – | In-memory filter for match_odds/scorer_props/snapshots. |
| limit | integer | No | 100 | Free tier max 100, api tier max 500. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/exchange?source=value"{
"success": true,
"data": [
{
"id": "flem_r5",
"raceKey": "flemington_5",
"venueSlug": "flemington",
"marketType": "WIN",
"bsp": 4.6,
"runnerName": "Fast Horse"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"source": "bsp",
"filters": {
"raceKey": "flemington_5"
},
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": ""
}
}
}GET/api/v1/exportAPI plan
Enterprise bulk data export as downloadable CSV (or JSON) from the BigQuery archive mirror — companion to the JSON-only /v1/bulk endpoint.
Tier detail: api (bulk_export is paid-only)
Uses guardV1Request(request, 'bulk_export', 25) — same 25-credit weighting as /v1/bulk. 503 if BQ_ENABLED is false or the dataset's BQ mirror doesn't exist yet ('Dataset not yet available'). CSV response sets filename `krok-{datasetKey}-{YYYY-MM-DD}.csv` and header X-Krok-Export-Rows with the actual row count. Cache 1800s for the json format variant.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| dataset | string | Yes | – | One of: game_results, player_props_results, model_track_record, gameday_odds_history, clv_archive, ev_hit_rates. 400 if missing/unknown. |
| limit | integer | No | 5000 | Capped at 5000 for both free and api tier (LIMIT_BY_TIER). |
| format | string | No | csv | csv (file download, Content-Disposition attachment) or json (standard v1 envelope via v1Success). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/export?dataset=value"{
"success": true,
"data": [
{
"id": "g1",
"sport_key": "americanfootball_nfl",
"home_score": 24,
"away_score": 17,
"completed": true
}
],
"meta": {
"dataset": "game_results",
"label": "Settled game results",
"limit": 5000,
"requested_limit": 5000
}
}GET/api/v1/extended-marketsFree tier
Derivative/extended odds markets excluded from the core h2h/spreads/totals feed — half/period/inning splits, soccer corners+cards, AFL/NRL winning_margin_bands, team_winning_margin, alt-totals — from our proprietary collection of 140+ AU bookmakers. Key market keys: winning_margin_bands (NRL 1-12/13+), team_winning_margin (Soccer Win By 2+/3+/4+), total_points_odd_even, ht_ft, highest_scoring_half.
Tier detail: free (advanced_stats is in FREE_TIER_FEATURES, so free keys reach this data too — the licence boundary here is Terms 6.1, not the tier gate)
Every row carries `source` ('scraped') and `redistributable` (bool) — licensed aggregator rows are FORBIDDEN_RESALE tier (b) under Terms 6.1, internal-use-only, never redistributable; KrokOdds' own scrape is redistributable. The three core markets (h2h, spreads, totals) are deliberately excluded — those live at /v1/odds. `meta.provider` field is @deprecated in favor of per-event `source`. Cache 300s free / 60s api; the two source tiers are fetched via Promise.allSettled so one failing doesn't take the other down.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | sport | string | No | – | Sport filter (used when event_id absent). |
| event_id | string | No | – | Point-read a single event across both tiers instead of a windowed scan. |
| market | string | No | – | Post-merge market-key filter, applied after both tiers are combined. |
| source | string | No | all | all | scraped. 'scraped' returns only the redistributable KrokOdds-owned tier. |
| limit | integer | No | 50 | Free tier max 50, api tier max 2000; applied per-tier before merge. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/extended-markets?sport_key | sport=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "evt-1",
"event_key": null,
"sport_key": "soccer_epl",
"sport_title": "EPL",
"home_team": "Arsenal",
"away_team": "Chelsea",
"commence_time": "2026-09-05T00:00:00.000Z",
"bookmakers": [
{
"key": "sportsbet",
"title": "Sportsbet",
"last_update": "2026-09-03T00:00:00.000Z",
"markets": [
{
"key": "corners_over_under",
"single_sided": false,
"outcomes": [
{
"name": "Over",
"description": "9.5",
"price": 1.9,
"point": 9.5,
"bet_link": "https://..."
}
]
}
]
}
],
"source": "scraped",
"redistributable": true
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": "soccer_epl",
"event_id": null,
"market": null,
"source": "all",
"provider": "scraped",
"providers": [
"scraped"
],
"timestamp": "2026-09-03T00:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 999,
"reset": ""
}
}
}GET/api/v1/meFree tier
API key introspection — returns the caller's tier, monthly usage/limit/remaining, per-request rate limit window, reset date, and a masked key preview.
Tier detail: free (no feature/tier gate at all — just requires a valid, active key)
CORRECTION vs. task brief: this route is API-KEY auth (X-API-Key header / apikey / api_key query param -> verifyApiKey), NOT Firebase session auth (getServerUser) — it does not import server-auth at all. Only webhooks/route.ts and clv-archive/[eventId]/route.ts use session auth in this batch of 14. PER_TIER_REQUEST_LIMITS here: free=100, api=10000 (note: api's 10000 differs from the 500-ish caps seen on most other free/api routes).
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/me"{
"success": true,
"data": {
"tier": "free",
"monthly_limit": 1000,
"current_usage": 42,
"remaining": 958,
"usage_pct": 4.2,
"reset_date": "2026-10-01T00:00:00.000Z",
"per_request_limit": 100,
"rate_limit_window": {
"limit": 60,
"remaining": 55,
"reset": "2026-09-03T00:05:00.000Z"
},
"api_key_preview": "krok...ab12"
},
"meta": {
"timestamp": "2026-09-03T00:00:00.000Z"
}
}GET/api/v1/statusFree tier
Public health/sync-freshness check — reports whether the arbs/snipes opportunity sync is healthy based on how recently the newest doc was written.
Tier detail: none — the only public, unauthenticated /v1 endpoint
force-dynamic route, no API key check at all. Rate-limited as tier 'free' under identifier 'anonymous' (shared bucket across all anonymous callers). 'degraded' means the newest arbs/snipes row is >5 minutes old. Reads Supabase supabaseMaxUpdatedAt('arbs'/'snipes') (indexed updated_at column) when isSupabase('opportunities'), else falls back to Firestore orderBy('detectedAt','desc').limit(1) — per an in-code historical note, an earlier version ordered by a field ('timestamp') the writer never set, which permanently reported 'degraded' (false alarm); see CLAUDE.md memory 'health-check-must-read-supabase'. Cached 30s via getCached().
curl \
"https://krokodds.com.au/api/v1/api/v1/status"{
"success": true,
"data": {
"status": "operational",
"sync": {
"healthy": true,
"last_sync_seconds_ago": 42
},
"timestamp": "2026-09-03T00:00:00.000Z"
},
"meta": {}
}GET/api/v1/stream/opportunitiesFree tier
Server-Sent Events (SSE) stream of live opportunity deltas (arbs/snipes/middles/low_holds) — initial full snapshot per type, then add/remove/update deltas on a 5s poll, with 15s keepalive heartbeats.
Tier detail: free (streaming is in FREE_TIER_FEATURES)
SSE, not a normal JSON envelope. Hard-caps the connection at 10 minutes wall-clock (MAX_STREAM_MS) then sends a 'bye' event and closes — clients should reconnect via EventSource's built-in retry. limit is fixed per tier (free=100, api=500), not client-adjustable. Uses polling (not Firestore onSnapshot) deliberately: Admin onSnapshot listeners can't be safely torn down across cold-start boundaries in the App Router serverless runtime. Rows older than 12h past commence_time (STALE_CUTOFF_MS) are filtered out of every snapshot/delta. Reads Supabase (supabaseLatestDocs) when isSupabase('opportunities'), else Firestore with server-side orderBy/where.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| types | string (comma-separated) | No | arbs,snipes,middles,low_holds | Subset of arbs|snipes|middles|low_holds to stream. Invalid/empty result -> 400. |
| sport_key | string | No | – | Filter events by sport. |
| min_value | number | No | 0 | Minimum edge value filter, ignored for the low_holds type (which sorts by holdPct instead). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/stream/opportunities?types=arbs%2Csnipes%2Cmiddles%2Clow_holds"{
"note": "This is an SSE stream (Content-Type: text/event-stream), not a single JSON response. Example frames:",
"frames": [
"event: ready\ndata: {\"types\":[\"arbs\",\"snipes\",\"middles\",\"low_holds\"],\"sport_key\":null,\"min_value\":0,\"poll_ms\":5000}\n\n",
"event: snapshot\ndata: {\"type\":\"arbs\",\"rows\":[{\"id\":\"a1\",\"value\":3.2,\"sport_key\":\"basketball_nba\"}]}\n\n",
"event: delta\ndata: {\"type\":\"arbs\",\"added\":[],\"removed\":[\"a2\"],\"updated\":[{\"id\":\"a1\",\"value\":3.5}]}\n\n",
": keepalive 1735689600000\n\n",
"event: bye\ndata: {\"reason\":\"max_stream_duration\"}\n\n"
]
}GETPOSTDELETE/api/v1/webhooksFree tier
CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-visible secret), DELETE soft-deletes (deactivates) one by id.
Tier detail: free (webhooks is in FREE_TIER_FEATURES) — but the tier is looked up from the user's own active API key, not from a key presented on this request
This route authenticates with the dashboard's browser session, not an API key – it isn't callable from third-party integrations.
Auth is Firebase session (getServerUser()) via getServerUser — NOT X-API-Key. Tier gate: GET reads tier from ANY active API key belonging to the user (getUserTier -> listUserApiKeys); POST requires the user to have at least one active API key (403 'No active API key' otherwise) and uses that key's tier. Writes are Supabase-only (supabaseUpsert on POST, supabaseMerge soft-delete on DELETE) with a Firestore-read fallback only on GET's list query and on ownership lookup in DELETE. GET response always strips the `secret` field (set to undefined) except at creation time in the POST response, which is the only time the secret is ever returned. Response envelope here does NOT use the standard {success,data,meta} shape on GET (bare {webhooks:[...]}) or POST (bare fields) — inconsistent with the rest of the v1 API.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| url (POST) | string | Yes | – | Destination HTTPS URL. Passed through checkWebhookUrl() SSRF guard (rejects internal/private-network targets) before being accepted. |
| events (POST) | string[] | No | ["arb","ev","middle"] | Event types to subscribe to. |
| minValue (POST) | number | No | 0 | Minimum edge value threshold to trigger delivery. |
| id (DELETE) | string | Yes | – | Webhook id to deactivate; 404 if the caller doesn't own it. |
curl \
# dashboard-only: uses your browser session, not an API key
-H "Content-Type: application/json" \
-d '{"url (POST)":"value","events (POST)":"[\"arb\",\"ev\",\"middle\"]","minValue (POST)":"0","id (DELETE)":"abc123"}' \
"https://krokodds.com.au/api/v1/api/v1/webhooks"{
"GET": {
"webhooks": [
{
"id": "wh_1",
"userId": "u1",
"url": "https://example.com/hook",
"events": [
"arb",
"ev",
"middle"
],
"minValue": 0,
"active": true,
"createdAt": "2026-09-01T00:00:00.000Z",
"deliveryCount": 12,
"failureCount": 0,
"secret": null
}
]
},
"POST": {
"id": "wh_2",
"url": "https://example.com/hook2",
"events": [
"arb",
"ev",
"middle"
],
"secret": "whsec_ab12...",
"message": "Webhook registered. Save your secret — it will not be shown again."
},
"DELETE": {
"success": true
}
}Additional Data
14 endpointsOdds history, steam-move detection, player prop stats/results, injuries, weather and other supporting feeds.
GET/api/v1/injuriesFree tier
Latest injury reports across sports/racing, filterable by sport and team. Reads from injury_reports.
NOT an alias of /api/v1/sports/injuries. Both read the same injury_reports table, but sports/injuries adds status_severity classification (out/doubtful/questionable/probable), body_part, and a cross-sport sport_breakdown summary in meta — its doc comment explicitly says it 'extends' this racing/general-purpose endpoint with multi-sport coverage. Treat as two distinct, overlapping endpoints, not a redirect/alias pair. Response cached 600s free / 120s api via unstable_cache.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (lowercased), e.g. "aussierules_afl". |
| team | string | No | – | Case-insensitive substring match on team name. |
| limit | integer | No | 100 | Max rows returned. Capped at 100 on free tier, 5000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/injuries?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "afl_2026_c-daniel-rioli",
"sport_key": "aussierules_afl",
"player_name": "Daniel Rioli",
"player_slug": "daniel-rioli",
"team": "Richmond",
"status": "Out",
"reason": "Hamstring strain",
"date": "2026-09-01",
"season": 2026,
"source": "official-team-report",
"updated_at": "2026-09-02T21:14:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 998,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/odds-historyAPI plan
Per-event opening vs. closing H2H odds with movement direction, deduped across re-keyed event ids.
Requires the paid 'api' tier (feature 'historical'). Two backing shapes depending on data source: primary reads the permanent `odds_history` table (snake_case fields, full bookmaker+opening_odds arrays, response includes them raw); fallback (if odds_history empty/errors) reads the legacy `gameday_odds_history` mirror (camelCase opens/latest/snapshots) then the BigQuery archive. Dedup: same fixture can carry multiple event_ids after upstream re-keying — stale rows are filtered out by default via staleFixtureIds() unless include_duplicates=true.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Filter by sport key (lowercased). |
| event_id | string | No | – | Filter to a single event. |
| include_snapshots | boolean | No | false | Set "true" to include the full time-series snapshot array (legacy gameday_odds_history source only). |
| include_duplicates | boolean | No | false | Set "true" to include stale rows for fixtures that were re-keyed with a new event_id upstream (flagged duplicate: true). Default is freshest-row-only. |
| limit | integer | No | 25 | Max rows. Capped at 25 on free tier, 1000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-history?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "e8f2a1-nrl-2026-09-04",
"event_id": "e8f2a1",
"sport_key": "rugbyleague_nrl",
"home_team": "Penrith Panthers",
"away_team": "Melbourne Storm",
"commence_time": "2026-09-04T09:30:00Z",
"opens": {
"home": 1.65,
"away": 2.25,
"opened_at_ms": null,
"home_implied_prob": 0.6061,
"away_implied_prob": 0.4444
},
"latest": {
"home": 1.58,
"away": 2.45,
"home_book": "Sportsbet",
"away_book": "Sportsbet",
"updated_at_ms": null,
"home_implied_prob": 0.6329,
"away_implied_prob": 0.4082
},
"movement": {
"home_delta": -0.07,
"away_delta": 0.2,
"home_direction": "shorten",
"away_direction": "drift"
},
"opening_odds": [],
"bookmakers": [],
"snapshot_count": 0,
"updated_at": "2026-09-03T02:11:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 25,
"requested_limit": 25,
"include_snapshots": false,
"include_duplicates": false,
"duplicates_found": 0,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 4990,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/odds/movementFree tier
Historical odds movement for one event/market from the append-only odds_archive firehose, with steam-move and line-drive detection.
Reads the `odds_archive` table (per CLAUDE.md: max-fidelity, Supabase-only, append-only, month-partitioned, never pruned). Rows grouped by (market, bookmaker) then by selection; steam_move = >=5% implied-probability shift across snapshots (n>=3), line_drive = >=60% of consecutive moves in the same direction (n>=2). Uses guardV1Request/v1Success helper (newer pattern) rather than the older per-route boilerplate.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| event_id | string | Yes | – | Event identifier. 400 error if missing. |
| market | string | No | – | Market filter, e.g. "h2h", "spreads", "totals". |
| bookmaker | string | No | – | Filter to a single bookmaker. |
| from | string (YYYY-MM-DD) | No | – | Start date filter on captured_at. |
| to | string (YYYY-MM-DD) | No | – | End date filter on captured_at (queried as to+'T23:59:59'). |
| limit | integer | No | 100 | Max snapshots per outcome group, clamped 1-500. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds/movement?event_id=evt_abc123"{
"success": true,
"data": [
{
"event_id": "e8f2a1",
"sport_key": "rugbyleague_nrl",
"home_team": "Penrith Panthers",
"away_team": "Melbourne Storm",
"market": "h2h",
"outcomes": [
{
"selection": "Penrith Panthers",
"snapshots": [
{
"timestamp": "2026-09-01T00:00:00Z",
"odds": 1.65,
"line": null,
"implied_prob": 0.6061,
"juice": null
},
{
"timestamp": "2026-09-03T02:00:00Z",
"odds": 1.58,
"line": null,
"implied_prob": 0.6329,
"juice": null
}
],
"steam_move": false,
"line_drive": true,
"open_odds": 1.65,
"close_odds": 1.58,
"move_pct": -4.24
}
],
"detected_at": "2026-09-01T00:00:00Z",
"bookmaker": "sportsbet"
}
],
"meta": {
"count": 1,
"tier": "free",
"event_id": "e8f2a1",
"market": null,
"bookmaker": null,
"from": null,
"to": null,
"limit": 100,
"steam_moves_detected": 0,
"line_drives_detected": 1,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/opportunity-historyAPI plan
Archived arb/+EV opportunities that have since expired (cleaned up after 24h).
Archive-tier endpoint (CREDIT_COST=5, comment: 'archive endpoint (historical opportunities)'). Feature 'historical' gates it to the paid 'api' tier only. Reads Supabase `opportunity_history` collection ordered by archivedAt desc.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| type | string | No | – | "arb" or "ev" only; any other value returns 400. |
| sport_key | string | No | – | Filter by sport key (lowercased). |
| since | string (ISO date) | No | – | Clamped by the key's historicalDays entitlement via clampHistoricalFrom(). |
| limit | integer | No | 50 | Max rows. Capped at 50 on free tier, 2000 on api tier (though feature itself is api-only). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunity-history?type=value"{
"success": true,
"data": [
{
"id": "arb-2026-09-02-x91k",
"type": "arb",
"sport_key": "basketball_nba",
"event_id": "b7c3d9",
"event_name": "Boston Celtics vs Denver Nuggets",
"commence_time": "2026-09-02T00:10:00Z",
"market_key": "h2h",
"profit_pct": 2.14,
"ev_pct": null,
"stake": 100,
"legs": [
{
"bookmaker": "Ladbrokes",
"outcome": "Boston Celtics",
"odds": 2.05
},
{
"bookmaker": "TAB",
"outcome": "Denver Nuggets",
"odds": 2.15
}
],
"archived_at": "2026-09-02T00:12:00.000Z",
"created_at": "2026-09-01T23:40:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 50,
"requested_limit": 50,
"type": null,
"sport_key": null,
"since": null,
"note": "Records cleaned up after 24 hours",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 200,
"remaining": 4995,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/player-props-resultsFree tier
Settled player-prop outcomes (hit/miss/actual value) per event.
Truth source is `player_props_results.outcomes[]` per repo memory (prop-backtest-data-model). event_id path is a single doc fetch keyed on eventId (slashes replaced with underscores, truncated to 1500 chars); without event_id it's a settled_at-ordered scan.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Post-filtered in memory (sportKey has no extracted column). |
| event_id | string | No | – | If set, does a direct doc lookup by id instead of a scan. |
| player_name | string | No | – | Case-insensitive partial match, filters within each event's outcomes array. |
| market | string | No | – | Exact match (lowercased) on outcome market_key. |
| since | string (ISO date) | No | – | Clamped by clampHistoricalFrom(); filters/orders on the indexed settled_at column. |
| limit | integer | No | 50 | Capped at 50 on free tier, 2000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/player-props-results?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"event_id": "b7c3d9",
"sport_key": "basketball_nba",
"event_name": "Boston Celtics vs Denver Nuggets",
"commence_time": "2026-09-02T00:10:00Z",
"completed_at": "2026-09-02T02:45:00Z",
"outcomes": [
{
"market_key": "player_points",
"player_name": "Jayson Tatum",
"line": 27.5,
"side": "over",
"hit": true,
"actual_value": 31
}
],
"outcome_count": 1,
"updated_at": "2026-09-02T03:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": null,
"event_id": null,
"player_name": null,
"market": null,
"since": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 998,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/player-props-statsFree tier
Aggregate hit/miss/push counts and hit rate per player/market/line/side.
Backed by Supabase collection `player_props_stats` (same table used by /api/v1/players/compare for its per-player prop breakdown). hit_rate = hits / (hits+misses), pushes excluded from denominator.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | |
| player_slug | string | No | – | Exact match, post-filtered in memory after fetch. |
| player_canonical | string | No | – | |
| market_key | string | No | – | |
| side | string | No | – | Must be "over" or "under" or it is ignored. |
| limit | integer | No | 50 | Capped at 50 on free tier, 2000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/player-props-stats?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "player_props_stats-jayson-tatum-player_points-27.5-over",
"sport_key": "basketball_nba",
"market_key": "player_points",
"player_name": "Jayson Tatum",
"player_slug": "jayson-tatum",
"canonical_key": "nba_jayson-tatum",
"line": 27.5,
"side": "over",
"hits": 34,
"misses": 21,
"pushes": 2,
"total": 57,
"decided": 55,
"hit_rate": 0.6182,
"last_result_at": "2026-09-02T03:00:00.000Z",
"updated_at": "2026-09-02T03:05:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"sport_key": null,
"player_slug": null,
"player_canonical": null,
"market_key": null,
"side": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 998,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/player-props/projectionsFree tier
AFL player projections branded as Krok Odds Projections, sourced from an advanced statistical model feed.
Route comment mandates branding this 'Krok Odds Projections' — never the underlying source name. Backed by Supabase collection `external_fryzigg_player_stats` (AFL only). `confidence` is a derived field = min(1, |Rating|/10), not a true model confidence. Cache TTL fixed at 3600s regardless of tier (not tier-scaled like most other v1 routes).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| season | integer | No | current year | Season year filter; 400 if non-numeric. |
| round | integer | No | – | Round number filter; 400 if non-numeric. |
| team | string | No | – | Exact match, case-insensitive, on team. |
| player | string | No | – | Case-insensitive partial match on player name. |
| limit | integer | No | 100 | Clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/player-props/projections?season=current%20year"{
"success": true,
"data": [
{
"id": "fryzigg_2026_r23_marcus-bontempelli",
"player_name": "Marcus Bontempelli",
"team": "Western Bulldogs",
"season": 2026,
"round": 23,
"game": "WB_vs_GEE_R23",
"date": "2026-08-30",
"opposition": "Geelong",
"kicks": 18,
"marks": 6,
"handballs": 12,
"tackles": 5,
"goals": 1,
"behinds": 0,
"hit_outs": 0,
"inside_50s": 4,
"clearances": 7,
"clangers": 2,
"rebound_50s": 1,
"frees_for": 2,
"frees_against": 1,
"time_on_ground_pct": 88,
"rating": 132.4,
"disposal_rating": 27.1,
"confidence": 1
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"season": 2026,
"round": null,
"team": null,
"player": null,
"source": "Krok Odds Projections",
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/players/compareFree tier
Compare 2-5 players' prop hit rates and season stats side by side; adds tennis H2H record and Elo when both players are ATP/WTA.
H2H + Elo enrichment only fires when sport_key contains 'tennis' AND exactly 2 players given; Elo is looked up only for the first player's slug in the current code (second player's Elo (_eloB) is fetched but discarded/unused). Elo source table is `tennis_players`, tried under id `atp_{slug}` then `wta_{slug}`. Uses guardV1Request/v1Success pattern.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| players | string (comma-separated slugs) | Yes | – | Min 2, max 5 slugs/canonical keys; 400 if <2 or >5. |
| sport_key | string | No | – | |
| season | string | No | – | Accepted but currently unused inside loadPlayerStats (param name is _season). |
| metrics | string (comma-separated) | No | – | Documented in the file header comment but not read/implemented in the current GET handler. |
| limit | integer | No | 20 | Max prop rows fetched per player, clamped 1-100. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/players/compare?players=lebron-james"{
"success": true,
"data": [
{
"slug": "novak-djokovic",
"name": "Novak Djokovic",
"sport_key": "tennis_atp",
"props": [
{
"market_key": "total_games_over_under",
"line": 21.5,
"side": "over",
"hit_rate": 62.5,
"sample": 16,
"hits": 10,
"misses": 6
}
],
"season_stats": {
"wins": 38,
"losses": 6
},
"h2h": {
"opponent_slug": "carlos-alcaraz",
"opponent_name": "Carlos Alcaraz",
"wins": 6,
"losses": 5,
"meetings": [
{
"date": "2026-07-14",
"winner": "Carlos Alcaraz",
"loser": "Novak Djokovic",
"score": "6-4 6-7 7-6",
"surface": "grass"
}
],
"surface_split": {
"hard": {
"a": 3,
"b": 3
},
"clay": {
"a": 2,
"b": 1
},
"grass": {
"a": 1,
"b": 1
}
},
"elo": {
"overall": 2231,
"hard": 2240,
"clay": 2180,
"grass": 2260
}
}
},
{
"slug": "carlos-alcaraz",
"name": "Carlos Alcaraz",
"sport_key": "tennis_atp",
"props": [
{
"market_key": "total_games_over_under",
"line": 21.5,
"side": "over",
"hit_rate": 58.3,
"sample": 12,
"hits": 7,
"misses": 5
}
],
"season_stats": {
"wins": 41,
"losses": 5
}
}
],
"meta": {
"count": 2,
"tier": "free",
"players": [
"novak-djokovic",
"carlos-alcaraz"
],
"sport_key": "tennis_atp",
"season": null,
"limit": 20,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 20,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/resultsAPI plan
Completed game results (scores, winner) from cold-storage archive.
Archive tier (CREDIT_COST=5, comment: 'archive endpoint (game results / cold storage)'). Empty results are refunded in full via refundApiKeyCredits and logged as HTTP-status 204 in the internal request log (though the actual response status returned is still 200). Rows with an empty resolved sport_key are silently dropped — flagged in-code as an upstream data-quality issue.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | Matched via an OR filter across sport_key / data->>sportKey / data->>sport_key to cover inconsistent writer casing. |
| since | string (ISO date) | No | – | Clamped by clampHistoricalFrom(); filters commence_time >= since. |
| limit | integer | No | 100 | Capped at 100 on free tier, 5000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/results?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "b7c3d9",
"game_id": "b7c3d9",
"sport_key": "basketball_nba",
"home_team": "Boston Celtics",
"away_team": "Denver Nuggets",
"home_score": 112,
"away_score": 104,
"winner": "home",
"commence_time": "2026-09-02T00:10:00Z",
"completed": true,
"completed_at": "2026-09-02T02:45:00Z",
"resolved": true,
"settled_at": "2026-09-02T02:50:00.000Z",
"resolution_source": "krok-odds",
"ingested_at": "2026-09-02T02:46:00.000Z",
"first_published_at": "2026-09-02T02:46:00.000Z",
"status": "final"
}
],
"meta": {
"count": 1,
"tier": "api",
"limit": 100,
"requested_limit": 100,
"since": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 500,
"remaining": 4995,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/sport-activityFree tier
Reference list of active/inactive sport keys and groupings (which sports currently have live coverage).
No tierAllowsFeature() call in this route at all — any valid API key on any tier can hit it, it's not gated behind a V1Feature. Reads Supabase `sport_activity_global`. Response uses `private, max-age` Cache-Control (not the 'no-store' pattern most other v1 routes use) with an explicit comment warning against ever making this response public/CDN-cached since it's auth-gated per key.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | If set, does a direct single-doc lookup instead of a scan. |
| group | string | No | – | Case-insensitive exact match on group, post-filtered in memory. |
| active | boolean | No | false | "true" restricts the scan to active=true rows. |
| limit | integer | No | 100 | Capped at 100 on free tier, 5000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sport-activity?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"key": "aussierules_afl",
"group": "Australian Rules",
"title": "AFL",
"description": "Australian Football League",
"active": true,
"has_outrights": true,
"updated_at": "2026-09-03T01:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"sport_key": null,
"group": null,
"active": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/steam-movesFree tier
Recently detected steam moves (sudden multi-book odds shortening/drifting) across sports.
Reads Supabase `steam_moves` collection (distinct from the raw-archive-derived /api/v1/odds/movement, which computes steam detection on the fly from odds_archive). This route consumes pre-computed steam-move docs instead.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | |
| event_id | string | No | – | |
| direction | string | No | – | Must be "shortening" or "drifting"; anything else is ignored (treated as null). |
| min_move_pct | number | No | 0 | Filters rows whose movePct is below this threshold. |
| bookmaker | string | No | – | Matched defensively against bookmaker/book/bookmakers/books fields, none of which are reliably populated per in-code comment. |
| limit | integer | No | 50 | Capped at 50 on free tier, 1000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/steam-moves?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "steam-2026-09-03-nrl-001",
"event_id": "e8f2a1",
"event": "Penrith Panthers vs Melbourne Storm",
"sport_key": "rugbyleague_nrl",
"sport_title": "NRL",
"outcome": "Penrith Panthers",
"direction": "shortening",
"old_odds": 1.75,
"new_odds": 1.55,
"move_pct": -11.43,
"old_implied_prob": 0.5714,
"new_implied_prob": 0.6452,
"implied_prob_delta": 0.0738,
"bookmaker_count": 6,
"commence_time": "2026-09-04T09:30:00Z",
"detected_at": "2026-09-03T03:40:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"direction": null,
"min_move_pct": null,
"filters": {
"sport_key": null,
"event_id": null,
"direction": null,
"min_move_pct": null,
"bookmaker": null
},
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 50,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/teams/canonicalFree tier
Canonical team id/slug/full-name mapping used to join team names across bookmaker feeds.
No tierAllowsFeature() gate in this route — reachable by any valid key/tier. Reads Supabase `team_canonical`. `external_id` is only populated when the doc id does NOT contain '__' (ids containing '__' are treated as composite/internal, not an external system id).
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport_key | string | No | – | |
| slug | string | No | – | Exact match. |
| name | string | No | – | Case-insensitive substring match on fullName, post-filtered in memory. |
| limit | integer | No | 100 | Capped at 100 on free tier, 5000 on api tier. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/teams/canonical?sport_key=aussierules_afl"{
"success": true,
"data": [
{
"id": "afl__brisbane-lions",
"sport_key": "aussierules_afl",
"external_id": null,
"participant_id": "afl__brisbane-lions",
"full_name": "Brisbane Lions",
"slug": "brisbane-lions",
"updated_at": "2026-08-20T00:00:00.000Z"
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 100,
"requested_limit": 100,
"sport_key": null,
"slug": null,
"name_contains": null,
"timestamp": "2026-09-03T04:00:00.000Z",
"rate_limit": {
"limit": 100,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/weatherFree tier
Raw observed + forecast weather for upcoming AU/NZ racing meetings.
NOT an alias of /api/v1/sports/weather-impact. This route only returns raw observed/forecast weather embedded on `racing_meetings/{id}` docs (BOM/Open-Meteo via racingWeatherSync), racing-only, per its own file-header comment. /api/v1/sports/weather-impact is a separate, cross-sport route that additionally reads `external_openmeteo_forecasts`, adds heuristic impact_tags/impact_notes (high_wind, heavy_rain, extreme_heat, cold_conditions, high_humidity, storm_risk), and covers AFL/NRL/NFL/MLB/EPL/NCAAF venues too, not just racing. Also uses `getAdminDb()`/Firestore as a fallback path if the Supabase read fails or isSupabase('weather') is false \u2014 the only one of these 13 routes with a live Firestore fallback.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| race_type | string | No | – | Must be T (thoroughbred), H (harness) or G (greyhound); other values ignored. |
| venue | string | No | – | Case-insensitive substring match. |
| state | string | No | – | Exact match, uppercased, e.g. NSW/VIC/QLD. |
| date | string (YYYY-MM-DD) | No | – | Exact match; also checked against the key's historicalDays window — 403 if too far in the past. |
| limit | integer | No | 50 | Capped at 50 on free tier, 1000 on api tier. |
| fields | string (comma-separated) | No | – | Field-projection allowlist parsed via parseFields()/projectRows() to trim the response shape. |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/weather?race_type=value"{
"success": true,
"data": [
{
"id": "randwick-2026-09-06",
"venue": "Royal Randwick",
"state": "NSW",
"race_type": "T",
"date": "2026-09-06",
"track_hint": "Good 4",
"weather_updated_at": "2026-09-03T01:00:00.000Z",
"observed": {
"summary": "Partly cloudy",
"temp_c": 17,
"rain_24h_mm": 0,
"wind_kmh": 14,
"wind_dir": "SE",
"station": "Sydney Airport",
"observed_at": "2026-09-03T00:00:00.000Z"
},
"forecast": {
"date": "2026-09-06",
"min_c": 11,
"max_c": 19,
"precis": "Shower or two",
"rain_chance_pct": 60,
"rain_range_mm": "1-5",
"state": "NSW"
}
}
],
"meta": {
"count": 1,
"tier": "free",
"limit": 50,
"requested_limit": 50,
"timestamp": "2026-09-03T04:00:00.000Z",
"coverage_note": "Racing meetings only (AU/NZ). Stadium/event-level weather not yet ingested.",
"rate_limit": {
"limit": 50,
"remaining": 999,
"reset": "2026-10-01T00:00:00.000Z"
}
}
}GET/api/v1/match-scheduleFree tier
Season schedule / fixture list for all sports backed by the match_schedule table.
Reads from match_schedule table (Supabase). Covers all sports. season_year and sport are Supabase-level filters; date/team/week/completed are post-filters. Cached 3600s.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| sport | string | No | – | Filter by sport_key (e.g. "americanfootball_nfl"). |
| date | string | No | – | Filter by match date (YYYY-MM-DD, starts-with match). |
| team | string | No | – | Filter by team name (partial match, case-insensitive). |
| season_year | number | No | – | Filter by season year (integer, e.g. 2026). |
| week | number | No | – | Filter by week/round number. |
| completed | string | No | – | "true" or "false" to filter by completion status. |
| limit | number | No | 100 | Max results, clamped 1-500 (MAX_LIMIT). |
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/match-schedule?sport=aussierules_afl"{
"success": true,
"data": [
{
"id": "sched_afl_2026_r23",
"sport_key": "aussierules_afl",
"event_id": "afl_20260906_abc",
"event_name": "Collingwood vs Carlton",
"short_name": "COL vs CAR",
"date": "2026-09-06",
"season_year": 2026,
"week": 23,
"week_text": "Round 23",
"venue": "MCG",
"venue_city": "Melbourne",
"home_team_id": "1",
"home_team_name": "Collingwood",
"home_score": null,
"away_team_id": "2",
"away_team_name": "Carlton",
"away_score": null,
"completed": false,
"status": "scheduled"
}
],
"meta": {
"count": 1,
"tier": "free",
"license": "krokodds-derived",
"filter": {
"sport": "aussierules_afl",
"date": null,
"team": null,
"season_year": null,
"week": null,
"completed": null
},
"sport_breakdown": {
"aussierules_afl": 1
}
}
}MCP Server
Krok Odds ships a Model Context Protocol server (mcp-server/ in the repo) that exposes the most useful data as MCP tools an AI agent can call directly – no manual curl-and-paste. It talks Streamable HTTP and authenticates with a bearer token.
Status: self-hosted, not yet on a public URL. The server queries Supabase directly (not through the public v1 API), so it currently runs with Krok Odds' own database credentials – you run your own instance rather than pointing at a hosted one. A managed, multi-tenant version that authenticates with your regular API key is on the roadmap.
Run it locally
cd mcp-server
npm install
export SUPABASE_URL=...
export SUPABASE_SERVICE_ROLE_KEY=...
export MCP_API_KEY=choose-a-bearer-token
export MCP_PORT=3100 # optional, defaults to 3100
npm run dev # tsx watch, live-reloads on saveConnect from Cursor
{
"mcpServers": {
"krok-odds": {
"url": "https://mcp.krokodds.com.au/mcp"
}
}
}Add to .cursor/mcp.json or Settings → MCP. Claude Desktop and VS Code need a stdio bridge – see full docs below.
Available tools (9)
| Tool | Description | Parameters |
|---|---|---|
| get_sports | List sports currently covered, with a count of open +EV opportunities per sport. | (none) |
| get_injuries | Current injury reports, optionally filtered by sport. | sport?, limit? (default 25, max 100) |
| get_upcoming_events | Upcoming events for a sport, with AI tip and consensus data. | sport?, limit? (default 20, max 100) |
| get_event_odds | All tracked bookmaker odds/markets for an event, matched by team names. | event, sport?, limit? (default 30, max 100) |
| get_value_bets | Find positive expected value (+EV) betting opportunities. | sport?, min_ev? (default 0), limit? (default 25, max 100) |
| get_sgm_picks | AI-generated same-game multi (SGM) suggestions for upcoming events. | sport?, tier? (safe|value|longshot), limit? (default 15, max 50) |
| get_player_props | Positive EV player prop opportunities. | sport?, min_ev? (default 0), limit? (default 25, max 100) |
| get_racing_picks | Today's AI racing win/place predictions. | date? (default today), venue?, limit? (default 30, max 100) |
| get_racing_meetings | Today's race meetings with race cards and runner lists. | date? (default today), state?, limit? (default 15, max 50) |
Examples
One real call per category – swap in your own key and filters.
curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/gameday/alt-lines?sport_key=aussierules_afl"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/opportunities?type=all"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/racing/arbs?venue=Randwick"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/odds-feed/bookmakers"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/sports?category=value"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/predictions/confidence?type=all"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/tennis/elo?player=lebron-james"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/historical/afl?apikey / api_key=value"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/reference/headshots?sport_key=aussierules_afl&apikey / api_key=value"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/prediction-markets/sentiment?sport_key=aussierules_afl"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/boxing/fight-results?X-API-Key=value"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/advanced-stats?source=value"curl \
-H "X-API-Key: YOUR_KEY" \
"https://krokodds.com.au/api/v1/api/v1/injuries?sport_key=aussierules_afl"Rate Limits & Headers
Every successful response carries the full set below so you can build your own backoff/metering without guessing.
| Header | Meaning |
|---|---|
| X-RateLimit-Tier | Your key’s tier – free or api |
| X-RateLimit-Limit | Per-request row cap for this tier/endpoint |
| X-RateLimit-Remaining | Monthly credits remaining after this request |
| X-RateLimit-Reset | ISO date-time the monthly counter resets |
| X-Credits-Cost | Credits debited for this request (0 if refunded for an empty result) |
| X-Credits-Remaining | Credits remaining this month, post-request |
| X-Cache-Age | Enforced cache TTL in seconds for this response (floors at 300s free / 15s paid) |
| X-Krok-Version | API version – currently "1" |
| X-Krok-Data-Source | Provenance of the data: proprietary, licensed, or derived |
| Retry-After | Seconds to wait before retrying (429 responses only) |
Changelog
Recent API-facing changes, most recent first.
- 2026-09-03Published this OpenAPI 3.1 spec and rebuilt this reference from a full read of every v1 route.
- 2026-09-02positive-ev: min_odds filter now actually applied (was parsed but silently ignored).
- 2026-09-0225 more v1 routes migrated from Firestore to Supabase-only reads.
- 2026-08-29MLB player-prop grading fixed for night games crossing UTC midnight.
- 2026-08-21SGM void window extended 48h → 7 days; recalibrated Platt scaling on same-game multis.
API FAQ
Auth, rate limits, credits, coverage and historical depth.
How do I authenticate with the Krok Odds API?
Every request carries your API key in the x-api-key header. Keys are issued from the API dashboard after signup and are scoped to a single plan – there is no OAuth flow and no separate token exchange. Keep the key server-side; it is a bearer credential and anyone holding it can spend your credits.
What are the rate limits?
Limits are enforced per key on a rolling window and vary by plan, with the free tier deliberately tight enough to test integration rather than run production traffic. Every response returns the remaining allowance in its headers, so poll those rather than hardcoding a cadence. A 429 means back off and retry with exponential delay.
How does the credit system work?
Requests spend credits rather than being counted as flat calls, because a single racing meeting response is far heavier than one head-to-head market. The free tier ships every live endpoint at 50 credits a day with no credit card, which is enough to verify response shapes against your model. The A$49/month plan covers full production access.
Which sports and leagues does the API cover?
181 sport and league keys, including AFL, NRL, NBA, NFL, MLB, NHL, EPL, A-League, La Liga, Serie A, Bundesliga, Champions League, BBL and international cricket, ATP/WTA tennis, UFC and boxing, Formula 1, golf, darts, esports, plus Australian thoroughbred, harness and greyhound racing. Query the sports endpoint for the live list rather than hardcoding keys.
How many bookmakers are in the odds responses?
140+ Australian bookmaker brands plus Betfair Exchange – every major corporate (Sportsbet, TAB, Ladbrokes, Neds, Bet365, PointsBet, BlueBet, BetRight, Betr, Unibet, Palmerbet, TABtouch, Dabble, BoomBet) and the white-label brands riding the major aggregator platforms. Prices are directly collected rather than resold, so the bookmaker list in a response is the live list, not a marketing figure.
How far back does the historical odds data go?
The odds archive is append-only and runs from January 2024, with 232K+ historical records and one row per bookmaker price per capture cycle rather than a single consensus line. That matters for backtesting: a consensus-only archive cannot reconstruct which book was the outlier, which is exactly the question an arbitrage or +EV backtest is asking.
Does the API include Australian racing?
Yes, and it is the main thing international odds feeds do not carry. Racing endpoints cover thoroughbred, harness and greyhound meetings across every Australian jurisdiction – fixed win and place across all books, Betfair Exchange back/lay and SP, market movers, scratchings, form, sectionals and runner metadata.
What is the difference between the odds endpoints and the opportunity endpoints?
The odds endpoints return raw prices and leave the analysis to you. The opportunity endpoints – arbitrage, positive EV, middles and low-holds – return the results of our own scanners over the same data, already devigged against a blended sharp baseline. Use the former if you have your own model; use the latter if you want our signals as JSON instead of as a dashboard.