RapidOddsAPI bookmaker odds API logoRapidOddsAPI

API Documentation

Everything you need to get started with the RapidOddsAPI

Getting Started

1. Create a free account to get your API key
2. Find your key in the dashboard
3. Start making requests

Authentication

Include your API key as a query parameter on every request. Your key starts with oa_ and can be found in your dashboard. Keep it secret — if compromised, regenerate it instantly.

Never expose your API key in client-side code (browser JavaScript, mobile apps, etc). Always make API calls from your backend server to keep your key secure.

Coverage

We cover 10+ leagues, 100+ bookmakers (including clones), and a wide range of market types including head-to-head, spreads, totals, and player props. Available markets vary by sport and bookmaker.

See our full coverage page for a complete list of all available leagues, bookmakers, and market types per sport.

Credits

Each request costs credits based on the number of market types and bookmakers requested:

credits = market_types × ⌈bookmakers / 5⌉

Every 5 bookmakers counts as 1 group (rounded up).

Examples

Market TypesBookmakersCredits
111
151
162
2104
3159

Credits are only deducted when data is returned. If your query returns no games, you are not charged.

Client Libraries

Official clients for Python and Node.js, plus an MCP server for AI assistants. They wrap the same API documented below, so anything you can do with a raw request you can do through them.

The client libraries are updated periodically, so new sports, markets and fields may reach the raw API first.

They save you writing the same plumbing every time: your key on every request, retries when one fails, reconnecting and re-subscribing when the WebSocket drops, game matching across bookmakers and they even include an arbitrage and value bet finder.

You do not need them. The API is plain HTTP and JSON, so if you would rather not add a dependency, or you work in another language, go straight to the endpoint reference. The cURL, Python and JavaScript examples further down use nothing but a HTTP client.

Python SDK

PyPIGitHub

The official Python client. Handles auth and retries for you, and returns typed responses. Requires Python 3.9 or newer.

pip install rapidoddsapi

Quickstart

from rapidoddsapi import RapidOddsAPI client = RapidOddsAPI(api_key="oa_your_api_key_here") odds = client.get_odds("AFL", ["head_to_head"], ["Sportsbet", "TAB", "Ladbrokes"]) for entry in odds["games"]: game = entry["game"] print(game["away_team"], "at", game["home_team"]) for book in entry["bookmakers"]: for market in book["markets"]: for outcome in market["outcomes"]: print(" ", book["name"], outcome["name"], outcome["price"])

It also wraps the WebSocket stream (reconnecting and re-subscribing on its own), the results API, and has arbitrage and value bet finders built in. See the README for the full reference.

Node.js SDK

npmGitHub

The official Node client. Written in TypeScript and ships its own type definitions, so it works the same from plain JavaScript. Requires Node 18 or newer.

npm install rapidoddsapi

Quickstart

import { RapidOddsAPI } from "rapidoddsapi"; const client = new RapidOddsAPI({ apiKey: "oa_your_api_key_here" }); const odds = await client.getOdds("AFL", ["head_to_head"], ["Sportsbet", "TAB", "Ladbrokes"]); for (const entry of odds.games) { console.log(entry.game.away_team, "at", entry.game.home_team); for (const book of entry.bookmakers) { for (const market of book.markets) { for (const outcome of market.outcomes) { console.log(" ", book.name, outcome.name, outcome.price); } } } }

CommonJS works too with const { RapidOddsAPI } = require("rapidoddsapi"). The WebSocket stream, results API, and the arbitrage and value bet finders are all included. See the README for the full reference.

MCP Server

PyPIGitHub

Connects RapidOddsAPI to an AI assistant over the Model Context Protocol, so it can pull live odds, scores and value bets on its own. Built on the Python SDK.

Claude Desktop

Add this to your claude_desktop_config.json, then restart Claude Desktop:

{ "mcpServers": { "rapidoddsapi": { "command": "uvx", "args": ["rapidoddsapi-mcp"], "env": { "RAPIDODDSAPI_API_KEY": "oa_your_api_key_here" } } } }

Tools

ToolCredits
list_sports0
get_oddsmarket_types × ⌈bookmakers / 5⌉
get_results1
find_arbitrage⌈bookmakers / 5⌉
find_value_bets⌈bookmakers / 5⌉

Every response ends with what the call cost and how many credits are left, so the assistant can see its own spend.

Rather do it yourself? No library is required. Everything from here down is the raw API, and it works from any language with a HTTP client.

Endpoint

One endpoint for everything. Choose your sport, pick the bookmakers and market types you want.

GET https://api.rapidoddsapi.com/sports/{sport_id}/markets?api_key={your_api_key}&market_type={market_type}&bookmaker={bookmaker}

Parameters

ParameterTypeRequiredDescription
sport_idpathYesSport identifier — see coverage
api_keyqueryYesYour API key
market_typequery (multi)YesMarket type. Repeat for multiple — see coverage
bookmakerquery (multi)YesBookmaker name. Repeat for multiple — see coverage

Example

GET https://api.rapidoddsapi.com/sports/NBA/markets?api_key=your_api_key&market_type=head_to_head&bookmaker=Sportsbet&bookmaker=DraftKings&bookmaker=Pinnacle
{ "sport": "NBA", "games": [ { "game": { "commence_time": "2026-02-14T10:00:00", "home_team": "Charlotte Hornets", "away_team": "Detroit Pistons" }, "bookmakers": [ { "name": "Sportsbet", "last_update": "2026-02-14T05:34:27", "markets": [ { "key": "head_to_head", "outcomes": [ { "name": "Detroit Pistons", "price": 1.65 }, { "name": "Charlotte Hornets", "price": 2.29 } ] } ] }, { "name": "DraftKings", "last_update": "2026-02-14T05:33:12", "markets": [ { "key": "head_to_head", "outcomes": [ { "name": "Detroit Pistons", "price": 1.62 }, { "name": "Charlotte Hornets", "price": 2.35 } ] } ] }, { "name": "Pinnacle", "last_update": "2026-02-14T05:34:01", "markets": [ { "key": "head_to_head", "outcomes": [ { "name": "Detroit Pistons", "price": 1.67 }, { "name": "Charlotte Hornets", "price": 2.31 } ] } ] } ] } ] }

Response

FieldTypeDescription
sportstringSport display name
games[]arrayList of games with odds
gameobjectGame details
commence_timestringGame start time (ISO 8601)
home_teamstringHome team name
away_teamstringAway team name
game_urlstringDeep link to this event on the bookmaker's site
bookmakers[]arrayRequested bookmakers with data for this game
namestringBookmaker name
last_updatestringLast time odds were updated (ISO 8601)
markets[]arrayMarkets for this bookmaker
keystringMarket type identifier
outcomes[]arrayOdds outcomes for this market
namestringOutcome name (team or Over/Under)
pricenumberDecimal odds
pointnumberLine value — spreads, totals, and player props
player_namestringPlayer name — player props only
team_namestringTeam name — team markets only
sizenumberContracts available at price — exchanges only
liquiditynumberDollar value available at price — exchanges only
alt_pricesarrayNext order book levels, each {price, size, liquidity} — exchanges only

Exchange lines are only returned when both sides are priced and their combined implied probability is 130% or less, to filter out lines nobody is actively trading. Thin lines can still appear, so check liquidity.

Rate Limiting

All plans are limited to 30 requests per second per API key. Exceeding this limit returns a 429 error.

Error Codes

CodeMeaning
401Invalid API key
403Subscription is not active
404Sport not found
422Missing required parameter (api_key, market_type, or bookmaker)
429Insufficient credits or rate limit exceeded
500Internal server error

Code Examples

curl "https://api.rapidoddsapi.com/sports/NBA/markets?\ api_key=your_api_key&\ market_type=head_to_head&\ bookmaker=Sportsbet&\ bookmaker=DraftKings"

Odds WebSocket

Real time odds pushed straight to you

Overview

The WebSocket feed pushes fresh odds data directly to you the moment each scraping cycle completes — no polling required. You connect once, subscribe to the sports and bookmakers you want, and receive updates automatically.

WebSocket access is included on the Pro and Elite plans. See pricing for details.

The data pushed over WebSocket is identical in structure to the REST endpoint response — the same sports, bookmakers, market types, and credit formula apply.

Connecting

Connect using your API key as a query parameter:

WSS wss://api.rapidoddsapi.com/ws?api_key=your_api_key

Once connected, the server sends a confirmation message:

{ "event": "connected", "tier": "pro", "message": "Send subscribe messages to start receiving odds data." }

Messages

Sending — Subscribe

After connecting, send a subscribe message to start receiving pushes for a sport. You can subscribe to multiple sports by sending one message per sport.

{ "action": "subscribe", "sport": "NBA", "bookmakers": ["Sportsbet", "DraftKings"], "market_types": ["head_to_head"] }

Sending — Unsubscribe

{ "action": "unsubscribe", "sport": "NBA" }

Receiving — Unsubscribed

{ "event": "unsubscribed", "sport": "NBA" }

Receiving — Subscribed

Sent immediately after a successful subscribe. Includes the credit cost per push for your subscription.

{ "event": "subscribed", "sport": "NBA", "bookmakers": ["Sportsbet", "DraftKings"], "market_types": ["head_to_head"], "credits_per_push": 1 }

Receiving — Odds Update

Sent each time a scraping cycle completes for your subscribed sport. The data field is identical in structure to the REST endpoint response.

{ "event": "odds_update", "sport": "NBA", "timestamp": "2026-02-25T10:00:00.000000", "credits_charged": 1, "data": { "sport": "NBA", "games": [ ... ] } }

Receiving — Error

{ "event": "error", "message": "Insufficient credits. Need 2, have 0." }

Credits

Credits are charged per push, not per connection. The same formula as the REST endpoint applies:

credits = market_types × ⌈bookmakers / 5⌉

The credit cost for your subscription is shown in the credits_per_push field of the subscribed confirmation. Credits are only deducted when a push contains data — if a scrape cycle returns no games, you are not charged.

If you have insufficient credits when a push is triggered, you will receive an error event and the push will be skipped.

Error Codes

WebSocket errors fall into two categories — connection rejections (before the connection is established) and in-session error events (sent as messages after connecting).

TypeCode / EventMeaning
Connection4001Close code sent before the connection opens: invalid or missing API key, inactive subscription, or plan does not include WebSocket access
In sessionerrorInsufficient credits, invalid sport, missing bookmakers or market types, or unknown action

Code Examples

import asyncio import websockets import json async def main(): url = "wss://api.rapidoddsapi.com/ws?api_key=your_api_key" async with websockets.connect(url) as ws: # Connected confirmation connected = json.loads(await ws.recv()) print(connected["message"]) # Subscribe to a sport await ws.send(json.dumps({ "action": "subscribe", "sport": "NBA", "bookmakers": ["Sportsbet", "DraftKings"], "market_types": ["head_to_head"] })) sub = json.loads(await ws.recv()) print(f"Subscribed — credits per push: {sub['credits_per_push']}") # Receive pushes async for message in ws: data = json.loads(message) if data["event"] == "odds_update": games = data["data"]["games"] print(f"{len(games)} games — credits charged: {data['credits_charged']}") asyncio.run(main())

Racing API

Pre-race AU and NZ racing odds, live, plus results

Overview

Pre-race fixed odds for every Australian and New Zealand thoroughbred and harness race and every Australian greyhound race: win and place prices for every runner, from every bookmaker we cover. Pick the code with the code parameter (default thoroughbred).

After each race, /results gives every runner's finishing position and margin, under the same race_id, kept for 30 days.

Included in Pro, Elite and the Racing plan. Same API key as everything else. Racing uses no credits.

Base URLhttps://api.rapidoddsapi.com/racing
Bookmakers123bet, Amused, Bet365, Betr, BetRight, ChaseBet, ColossalBet, Dabble, EliteBet, Ladbrokes, NextBet, PalmerBet, Picklebet, PointsBet, Sportsbet, SwiftBet, TAB, Unibet
Which racesEach race appears about 24 hours before it starts and stays listed up to 15 minutes after its start time, so late starts are covered. closed: true once it has jumped
TimesUTC, for example 2026-10-19T04:20:00Z
Start timesA race's start_time is the time most bookmakers agree on, so a delay shows once most have it. Each bookmaker's own time is in its start_time
Scratched runnersscratched: true with win and place set to null
ResultsFinishing position and margin for every runner, usually within a few minutes of the race. Kept 30 days. See Results
FlucsPrice changes for every runner at every bookmaker, for today's races. See Flucs
Abandoned racesabandoned: true with every win and place set to null. The race stays listed until 15 minutes after its start time
Jumped racesclosed: true once the race has jumped. Each bookmaker's last prices are kept

Runners are numbered the same at every bookmaker, so compare prices by number. Track names are spelled the same at every bookmaker too.

Meetings

What's on: every track and its upcoming races. No odds.

GET https://api.rapidoddsapi.com/racing/meetings?api_key={your_api_key}
ParameterRequiredDescription
api_keyYesYour API key
dateNoMeeting date YYYY-MM-DD (Sydney). Default today
countriesNoAU, NZ or AU,NZ. Default both
codeNothoroughbred (default), harness or greyhound. With race_id the code comes from the id
{ "code": "thoroughbred", "date": "2026-10-19", "meetings": [ { "track": "Orange", "country": "AU", "races": [ { "race_id": "thoroughbred_2026-10-19_Orange_3", "race_number": 3, "start_time": "2026-10-19T04:20:00Z", "abandoned": false, "closed": false, "bookmakers": ["Betr", "Ladbrokes", "PointsBet", "Sportsbet", "TAB"] } ] } ] }

Races

Odds for one race, or for every race at one meeting. Pass either race_id or track.

GET https://api.rapidoddsapi.com/racing/races?api_key={your_api_key}&track=Orange
ParameterRequiredDescription
api_keyYesYour API key
race_idOne ofOne race, from /meetings
trackOne ofEvery upcoming race at this track. Not case sensitive
dateNoWith track: meeting date YYYY-MM-DD (Sydney). Default today
bookmakersNoComma separated, for example TAB,Sportsbet. Default all
countriesNoAU, NZ or AU,NZ. Default both
codeNothoroughbred (default), harness or greyhound. With race_id the code comes from the id
{ "races": [ { "race_id": "thoroughbred_2026-10-19_Orange_3", "code": "thoroughbred", "track": "Orange", "country": "AU", "race_number": 3, "start_time": "2026-10-19T04:20:00Z", "abandoned": false, "closed": false, "bookmakers": [ { "bookmaker": "TAB", "status": "open", "start_time": "2026-10-19T04:20:00Z", "url": "https://www.tab.com.au/racing/2026-10-19/ORANGE/ORG/R/3", "last_update": "2026-10-19T04:14:27Z", "runners": [ { "number": 4, "name": "Skeldale House", "scratched": true, "win": null, "place": null }, { "number": 5, "name": "Three Wishes", "scratched": false, "win": 5.5, "place": 2.3 }, { "number": 7, "name": "Invasion", "scratched": false, "win": 2.6, "place": 1.5 } ] } ] } ] }

last_update is when that bookmaker's prices for the race were last refreshed. The bookmaker's start_time is the start time that bookmaker shows; it can lag behind for a minute or two when a race is delayed. url is the race's page on that bookmaker's website (null when we have no link for it).

status is open, closed (that bookmaker closed betting at the jump; its prices are the last ones offered) or abandoned for that bookmaker. The race's closed and abandoned are true once two or more bookmakers say so, or the only bookmaker on the race does. They count every bookmaker, even when you filter with bookmakers. An abandoned race is never closed.

Results

Finishing positions and margins for every runner. Every parameter is optional: with none you get every result so far today. Results are kept for 30 days, under the same race_id as the odds.

GET https://api.rapidoddsapi.com/racing/results?api_key={your_api_key}&track=Newcastle
ParameterRequiredDescription
api_keyYesYour API key
race_idNoOne race. Not with track
trackNoOne meeting. Not case sensitive. Default every track
dateNoMeeting date YYYY-MM-DD (Sydney), any day in the last 30. Default today
countriesNoAU, NZ or AU,NZ. Default both
codeNothoroughbred (default), harness or greyhound. With race_id the code comes from the id
{ "results": [ { "race_id": "thoroughbred_2026-10-26_Newcastle_1", "code": "thoroughbred", "track": "Newcastle", "country": "AU", "race_number": 1, "start_time": "2026-10-26T02:50:00Z", "status": "final", "runners": [ { "position": 1, "number": 6, "name": "Astrogazer", "margin": null, "scratched": false }, { "position": 2, "number": 8, "name": "Smart Jazz", "margin": 0.49, "scratched": false }, { "position": 3, "number": 3, "name": "D'Oro Choice", "margin": 1.21, "scratched": false }, { "position": null, "number": 4, "name": "Rebel's Edge", "margin": null, "scratched": true } ], "last_update": "2026-10-26T03:02:21Z" } ] }

status is interim (usually the winner only, a couple of minutes after the race), final, or abandoned (no runners). A final result often lists the first four placings first and the rest of the field a few minutes later.

Runners are in finishing order, with scratched runners last. margin is lengths behind the winner. A dead heat shows as two runners with the same position. Join to the odds on race_id and runner number.

Flucs

Price fluctuations for one race: each bookmaker's first recorded price for every runner, then every change. Kept for 3 days, for showing flucs on today's races. Not a historical archive.

GET https://api.rapidoddsapi.com/racing/flucs?api_key={your_api_key}&race_id=thoroughbred_2026-10-27_Flemington_7
ParameterRequiredDescription
api_keyYesYour API key
race_idYesOne race, from /meetings or /races
bookmakersNoComma separated, for example TAB,Sportsbet. Default all
{ "race_id": "thoroughbred_2026-10-27_Flemington_7", "start_time": "2026-10-27T04:00:00Z", "bookmakers": [ { "bookmaker": "TAB", "runners": [ { "number": 1, "flucs": [ { "time": "2026-10-26T22:10:00Z", "win": 4.6, "place": 1.85 }, { "time": "2026-10-27T01:15:00Z", "win": 4.4, "place": 1.8 }, { "time": "2026-10-27T03:58:40Z", "win": 4.2, "place": 1.75 } ] } ] } ] }

flucs starts with the first price we recorded, up to 24 hours before the race, and adds a point only when the price changes. Races priced earlier than that may already have moved before the first point. Prices are checked hourly when a race is more than 12 hours away, then more often as it gets closer: every 15 minutes, 5 minutes, 2 minutes, 1 minute, and every 10 seconds in the last 5 minutes until the jump. The last point is the latest recorded price. For live prices use /races or the WebSocket. Scratched runners get no new points.

WebSocket

Live push. Every time a bookmaker changes a race, you get that bookmaker's full runner list for that race.

wss://api.rapidoddsapi.com/racing/ws?api_key={your_api_key}

Subscribe

Subscribe by track. A track subscription covers every upcoming race there, today and on future days, so you only subscribe once per connection. "*" means every track.

// Every Australian race, every day { "action": "subscribe", "tracks": "*", "countries": ["AU"] } // Two tracks, TAB and Sportsbet only { "action": "subscribe", "tracks": ["Flemington", "Randwick"], "bookmakers": ["TAB", "Sportsbet"] } // One race { "action": "subscribe", "races": ["thoroughbred_2026-10-19_Orange_3"] }
FieldDescription
tracks"*" or a list of track names. Give tracks, races or both
racesA list of race_ids
countriesOptional. ["AU"], ["NZ"] or both. Default both
bookmakersOptional. Default all
codeOptional. thoroughbred (default), harness or greyhound. One code per subscribe - send one subscribe per code to follow several on the same connection
resultsOptional. true also sends race_result messages for these races. Default off

Each subscribe adds to what you already have. To stop, send {"action": "unsubscribe", "tracks": ["Orange"]} or {"action": "unsubscribe", "all": true}. After a reconnect, send your subscribe again.

Messages you receive

eventWhen
connectedStraight after you connect
subscribedAfter each subscribe. races holds every matching upcoming race right now, in the same shape as /races
race_updateA bookmaker changed a race. One bookmaker, one race
race_abandonedA race was abandoned. Sent once per race, whatever your bookmakers filter
race_closedA race has jumped (two or more bookmakers closed betting). Sent once per race, whatever your bookmakers filter
race_resultOnly with "results": true. A race's result was posted or updated (interim, then final). Same shape as /results
unsubscribedAfter an unsubscribe. Lists what you are still subscribed to
errorA message you sent was invalid. message says why
{ "event": "race_update", "race_id": "thoroughbred_2026-10-19_Orange_3", "code": "thoroughbred", "track": "Orange", "country": "AU", "race_number": 3, "start_time": "2026-10-19T04:20:00Z", "abandoned": false, "closed": false, "bookmaker": "PointsBet", "status": "open", "book_start_time": "2026-10-19T04:20:00Z", "url": "https://pointsbet.com.au/racing/Thoroughbred/AUS/Orange/race/117003311", "last_update": "2026-10-19T04:16:19Z", "runners": [ { "number": 4, "name": "Skeldale House", "scratched": true, "win": null, "place": null }, { "number": 5, "name": "Three Wishes", "scratched": false, "win": 5.5, "place": 2.15 } ] }

In a race_update, start_time is the race's start time (the one most bookmakers agree on) and book_start_time is that bookmaker's own. New races arrive as race_update messages as soon as a bookmaker lists them. There is no separate "new race" event.

{ "event": "race_abandoned", "race_id": "thoroughbred_2026-10-20_Grafton_8", "code": "thoroughbred", "track": "Grafton", "country": "AU", "race_number": 8, "start_time": "2026-10-20T06:30:00Z" }

Each bookmaker that abandons the race also sends a race_update with status: "abandoned" and no prices.

{ "event": "race_closed", "race_id": "greyhound_2026-10-27_Gosford_7", "code": "greyhound", "track": "Gosford", "country": "AU", "race_number": 7, "start_time": "2026-10-27T06:15:00Z" }

Each bookmaker that closes the race also sends one race_update with status: "closed" and its last prices. Races keep updating for up to 15 minutes after their start time, then stop. When a race is delayed, the 15 minutes count from the new time as soon as two bookmakers show it.

// Odds + results for every Australian race { "action": "subscribe", "tracks": "*", "countries": ["AU"], "results": true } { "event": "race_result", "race_id": "thoroughbred_2026-10-26_Newcastle_1", "code": "thoroughbred", "track": "Newcastle", "country": "AU", "race_number": 1, "start_time": "2026-10-26T02:50:00Z", "status": "final", "runners": [ { "position": 1, "number": 6, "name": "Astrogazer", "margin": null, "scratched": false }, { "position": 2, "number": 8, "name": "Smart Jazz", "margin": 0.49, "scratched": false } ], "last_update": "2026-10-26T03:02:21Z" }

A race can send several race_result messages: interim, then final, then final again when the rest of the field is added. Each one holds the full runner list, so just keep the latest.

Limits & Errors

REST120 requests per minute per API key. No credits, no monthly cap
WebSocket3 connections open at once per account. Messages are not counted

REST errors

StatusMeaning
400Bad parameters, for example both race_id and track, or a wrong date format
401Missing or invalid API key
403Your plan does not include racing
429Over 120 requests a minute. The Retry-After header says how many seconds to wait

Errors come back as {"detail": "..."}.

WebSocket close codes

CodeMeaning
4001Invalid API key, or your plan does not include racing
4029You already have 3 connections open

Code Examples

curl "https://api.rapidoddsapi.com/racing/meetings?api_key=your_api_key" curl "https://api.rapidoddsapi.com/racing/races?api_key=your_api_key&track=Flemington" curl "https://api.rapidoddsapi.com/racing/results?api_key=your_api_key&track=Flemington"

Results API

Live scores and player stats in real time

Overview

The Results API returns live scores, period breakdowns, and full player stat lines for every game we cover. Use it to track games in play, or to settle bets once a game finishes.

It runs on its own endpoint but uses the same API key as the odds API, so there is nothing extra to set up.

See our full coverage page for the sports and player stats available. Requesting a sport we do not cover returns a 404 listing the sports that are.

Endpoint

One endpoint. Filter by status to get only the games you care about, and use include to control how much data comes back.

GET https://api.rapidoddsapi.com/results/{sport}?api_key={your_api_key}

Parameters

ParameterTypeRequiredDescription
sportpathYesSport identifier, for example AFL or MLB. Not case sensitive
api_keyqueryYesYour API key, the same one used for odds
statusqueryNoall (default), live, concluded, or upcoming
includequeryNoComma separated: scores, players. Defaults to both
game_idqueryNoReturn a single game.
roundqueryNoFilter to one round number. Only for sports played in rounds. Sports without them return nothing for any value, and carry a null round_number
daysqueryNoOnly return concluded games from the last N days. Live and upcoming games are always returned. Omit for everything we hold

We keep concluded games for 7 days so there is a window to settle against, then they are removed. Use days to trim that down when you only care about recent games, for example days=1 on a busy slate to get today's games without a week of history behind it. It is an upper bound rather than a guarantee, so asking for more days than we hold is not an error, you just get everything there is.

Game status values

ValueMeaningIncludes score and players?
SCHEDULEDFixtured, not yet startedNo
LIVEIn progressYes, partial and updating
CONCLUDEDFinishedYes, final

These three values are the same across every sport, whatever the underlying competition calls them. The upcoming filter maps to SCHEDULED. Games that have not started carry only the game block, with no score or players.

Example

GET https://api.rapidoddsapi.com/results/AFL?api_key=your_api_key&status=concluded&include=scores,players
{ "sport": "AFL", "games": [ { "game": { "game_id": 8216, "commence_time": "2026-07-23T09:30:00", "last_update": "2026-07-25T07:53:41", "home_team": "Adelaide Crows", "away_team": "Collingwood", "status": "CONCLUDED", "round_number": 20 }, "score": { "home": { "team": "Adelaide Crows", "goals": 11, "behinds": 8, "points": 74 }, "away": { "team": "Collingwood", "goals": 16, "behinds": 12, "points": 108 }, "home_by_period": [ { "period": 1, "goals": 5, "behinds": 3, "points": 33 }, { "period": 2, "goals": 2, "behinds": 3, "points": 15 }, { "period": 3, "goals": 2, "behinds": 0, "points": 12 }, { "period": 4, "goals": 2, "behinds": 2, "points": 14 } ], "away_by_period": [ { "period": 1, "goals": 3, "behinds": 2, "points": 20 }, { "period": 2, "goals": 3, "behinds": 2, "points": 20 }, { "period": 3, "goals": 4, "behinds": 4, "points": 28 }, { "period": 4, "goals": 6, "behinds": 4, "points": 40 } ], "period_state": [ { "period": 1, "completed": true, "period_seconds_elapsed": 1901 }, { "period": 2, "completed": true, "period_seconds_elapsed": 1846 }, { "period": 3, "completed": true, "period_seconds_elapsed": 2021 }, { "period": 4, "completed": true, "period_seconds_elapsed": 1950 } ], "totals": { "full_time": { "goals": 27, "behinds": 20, "points": 182 }, "half_time": { "goals": 13, "behinds": 10, "points": 88 }, "by_period": [ { "period": 1, "goals": 8, "behinds": 5, "points": 53 }, { "period": 2, "goals": 5, "behinds": 5, "points": 35 }, { "period": 3, "goals": 6, "behinds": 4, "points": 40 }, { "period": 4, "goals": 8, "behinds": 6, "points": 54 } ] } }, "players": [ { "name": "Nick Daicos", "team": "Collingwood", "player_disposals": 39, "player_kicks": 22, "player_handballs": 17, "player_goals": 1, "player_marks": 6, "player_tackles": 3, "player_clearances": 7, "player_fantasy_points": 116, "behinds": 0 } ] } ] }

The same response for another sport

Same structure, same field names. Only the scoring fields, the stat fields and the name of the mid-game total change. Here a LIVE MLB game, where a period is an inning.

{ "sport": "MLB", "games": [ { "game": { "game_id": 401816253, "commence_time": "2026-07-25T22:10:00", "last_update": "2026-07-26T00:09:15", "home_team": "Tampa Bay Rays", "away_team": "Cleveland Guardians", "status": "LIVE", "round_number": null }, "score": { "home": { "team": "Tampa Bay Rays", "runs": 3, "hits": 9, "errors": 0 }, "away": { "team": "Cleveland Guardians", "runs": 0, "hits": 4, "errors": 0 }, "home_by_period": [ { "period": 1, "runs": 1 }, { "period": 2, "runs": 0 } ], "away_by_period": [ { "period": 1, "runs": 0 }, { "period": 2, "runs": 0 } ], "period_state": [ { "period": 1, "completed": true }, { "period": 6, "completed": true }, { "period": 7, "completed": false, "half": "Bottom" } ], "totals": { "full_time": null, "first_5_innings": { "runs": 2 }, "by_period": [ { "period": 1, "runs": 1 }, { "period": 2, "runs": 0 } ] } }, "players": [ { "name": "Steven Kwan", "team": "Cleveland Guardians", "batter_hits": 1, "batter_runs": 0, "batter_rbis": 0, "batter_home_runs": 0, "batter_walks": 0, "batter_strikeouts": 1, "batter_hits_runs_rbis": 1, "batter_singles": 1, "batter_total_bases": 1, "doubles": 0, "triples": 0 }, { "name": "Cole Sulser", "team": "Tampa Bay Rays", "pitcher_strikeouts": 1, "pitcher_earned_runs": 0, "pitcher_hits_allowed": 0, "pitcher_walks_allowed": 0, "pitcher_outs": 3 } ] } ] }

Note full_time is null because the game is still live, while first_5_innings is populated because those innings have finished. Innings are trimmed here for length.

Response

FieldTypeDescription
sportstringSport display name
games[]arrayList of games
gameobjectAlways present
game_idintegerStable identifier, use with the game_id parameter
commence_timestringScheduled start (UTC)
last_updatestringWhen we last refreshed this game (UTC)
home_team / away_teamstringTeam names, standardised to match the odds API
statusstringSee the status table above
round_numberinteger or nullRound this game belongs to. Null for sports not played in rounds, such as MLB
scoreobjectPresent once a game starts, if include has scores
home / awayobjectTeam name plus that sport's scoring fields. AFL goals, behinds, points. MLB runs, hits, errors
home_by_period[] / away_by_period[]arrayThat team's score in each period. Per period, not cumulative
period_state[]arrayPer period completed flag, plus whatever live detail that sport has: a clock field (see the clock table), or half on the MLB inning in progress
totalsobjectBoth teams combined, see Settling Bets below
full_timeobject or nullFinal match total. Null until the game is CONCLUDED
half_time / first_5_inningsobject or nullThe sport's mid-game total, null until every period it covers has finished. half_time covers the first half, which is however many periods that sport's first half contains. That is period 2 in a sport played in quarters, or period 1 in a sport played in halves. MLB carries first_5_innings instead, which waits for inning 5
by_period[]arrayCombined total for each period played so far
scoring_events[]arrayEvery score in the match in the order it happened, where that sport has one worth recording. Each entry carries order, type, the scoring player, their team, the period and the clock. Absent for sports we do not track this for
players[]arrayEvery player who has taken the field, if include has players
name / teamstringPlayer name and their team, both standardised
{stat_name}integer or booleanOne field per player prop market we cover for that sport, named to match the market key, for example player_disposals or batter_total_bases. Counting stats are integers. A market that asks whether a player did something rather than how often, such as scoring first, is a boolean. See coverage

Periods

The response shape is identical for every sport. A period is whatever that sport divides a game into, whether a quarter, a half or an inning, and by_period always holds the raw per-period values you can build any period market from. Only the stat field names change between sports. Because a period means different things, treat the period numbers as opaque: period 2 is half time in one sport and quarter time in another.

The live clock

Inside period_state[], each sport carries the clock its own competition keeps. They are not interchangeable, so the field name states both things you need to know: which window it measures and which way it counts, as {scope}_seconds_{direction}.

FieldMeasuresCountsResets each period?
period_seconds_elapsedTime played in the current periodUp, from 0Yes
period_seconds_remainingTime left in the current periodDown, to 0Yes
match_seconds_elapsedTime played since the start of the matchUp, from 0No
display_clockThe clock as that sport writes it, for example "43'" or "8:57"Stringn/a
halfWhere an MLB inning is up to: Top, Bottom, Middle or EndStringn/a

A sport carries whichever of these its competition actually publishes, so read the field that is present rather than assuming one. Baseball has no clock at all and carries half instead. Only the period in progress carries a clock, with one exception: AFL keeps period_seconds_elapsed on finished quarters too, because its feed records how long each one ran.

The clock is for display. Nothing settles from it, and it is not a countdown to the end of a period unless the field says remaining. AFL is the clearest case: its clock includes time-on, so a nominal 20 minute quarter runs anywhere from about 1760 to 2020 seconds and there is no fixed length to count down from. Use completed to know a period is over.

Player fields

A player who fills two roles appears once with both sets of fields. An MLB two-way player carries the batter_ and pitcher_ fields together, and fields for a role a player did not fill are simply absent rather than zero.

Markets about who scored first read the player field, not scoring_events. A market like player_1st_tryscorer is already resolved to a boolean on that player, so you never have to walk the list yourself. scoring_events is there to show the sequence live, and to check a settlement against if one is ever queried.

Timestamps

Timestamps are UTC and use the same format as the odds API. Start times for the same game can differ slightly between the two, so match games on team names plus a time window rather than an exact timestamp. Our guide on matching games across bookmakers shows the approach.

Settling Bets

If you are tracking bets taken from the odds API and want to settle them automatically, this section covers what you need. The two APIs share standardised team and player names, so a bet recorded against a market key and a selection can be resolved straight from the results data once the game is done.

Only settle on CONCLUDED games

A named total is null until the period it describes has finished. full_time stays null until the game is CONCLUDED, and a mid-game total stays null until its own periods are done. That is deliberate, so a live scoreline can never be mistaken for a final one. For a live running total, sum by_period yourself.

Do not hardcode how many periods a half is. half_time waits for period 2 in a sport played in quarters, but only period 1 in a sport played in halves, so a rule like "settle once period 2 is done" silently never fires for the second group. Read period_state[].completed and let the null tell you: if half_time is populated, that half is over and it is safe to settle.

totals = entry["score"]["totals"] if totals["full_time"] is not None: settle(total_points=totals["full_time"]["points"]) # safe, game is final else: running = sum(p["points"] for p in totals["by_period"]) # display only # "points" here is the scoring field for this sport. MLB uses "runs".

Market to field map

A few examples of how odds API markets line up with the results data. The rest follow the same patterns.

Dots below show where a value sits in the nested response, so score.totals.half_time.points means the points value inside half_time, inside totals, inside score.

MarketSettle from
head_to_headHigher of score.home and score.away on that sport's scoring field, so points in most sports and runs in MLB
alternate_linesSelected team's score minus the opponent's, plus that outcome's point. See below
alternate_total_pointsscore.totals.full_time.points
alternate_total_points_1st_halfscore.totals.half_time.points
player_disposalsplayers[].player_disposals
alternate_total_runsscore.totals.full_time.runs
alternate_total_runs_1st_5_inningsscore.totals.first_5_innings.runs
alternate_total_runs_1st_1_inningsPeriod 1 of score.totals.by_period
alternate_team_total_runsscore.home.runs or score.away.runs
batter_total_basesplayers[].batter_total_bases
pitcher_strikeoutsplayers[].pitcher_strikeouts

The pattern holds across the rest. Team markets read from score.home or score.away, a market covering part of a game reads that sport's named total (half_time, or first_5_innings for MLB) or the matching entries in the period arrays, single period markets read the matching entry in score.totals.by_period, and every player market maps to the stat field of the same name.

Player stat fields are named after the market key, so batter_hits settles from players[].batter_hits with no lookup table. A _milestones market settles from the same field as its base market.

Player and team names are standardised across both APIs, so they join directly. See the coverage page for the full market list.

Handicaps in detail

Handicaps are the one case where the match margin alone is not enough. Each outcome carries its own signed point, so a bet is settled from the point of view of the team that was backed, not the home team.

margin = points(selected team) - points(opponent) result = margin + outcome.point > 0 win < 0 lose = 0 push (only possible on whole number lines)

Using the example game above, Adelaide Crows 74 and Collingwood 108:

BetMarginPlus pointResult
Collingwood -20.5108 - 74 = +3434 - 20.5 = +13.5Win
Adelaide Crows +20.574 - 108 = -34-34 + 20.5 = -13.5Lose
Adelaide Crows +40.574 - 108 = -34-34 + 40.5 = +6.5Win
Adelaide Crows +3474 - 108 = -34-34 + 34 = 0Push

First half handicaps settle the same way, using the first half's entries in the two _by_period arrays in place of the final scores. That is periods 1 and 2 in a sport played in quarters, but period 1 alone in a sport played in halves, so read period_state[] rather than assuming two.

Credits

Results requests cost a flat 1 credit each, no matter how many games come back or how much detail you include.

credits = 1 per request

As with odds, credits are only deducted when data is returned. Polling status=live with no games in play costs nothing.

Results and odds requests draw from the same monthly credit allowance. See pricing.

Error Codes

CodeMeaning
400Invalid status or include value
401Invalid API key
403Subscription is not active
404Sport not found
422Missing api_key
429Insufficient credits or rate limit exceeded
500Internal server error

An unknown game_id is not an error. It returns 200 with an empty games array, and costs no credits.

Code Examples

curl "https://api.rapidoddsapi.com/results/AFL?\ api_key=your_api_key&\ status=live&\ include=scores,players"

Results WebSocket

Live scores pushed to you as they happen

Connecting

The results WebSocket pushes fresh scores and player stats every time a scrape cycle completes. See coverage for update frequencies. It is a separate endpoint from the odds WebSocket, and is included on the Pro and Elite plans.

WSS wss://api.rapidoddsapi.com/results-ws?api_key=your_api_key

Once connected, the server sends a confirmation message:

{ "event": "connected", "tier": "pro", "message": "Send subscribe messages to start receiving results data." }

Messages

Sending — Subscribe

The status, include and days fields work exactly as they do on the REST endpoint, and all are optional. Subscribing to live is the usual choice for score tracking.

{ "action": "subscribe", "sport": "AFL", "status": "live", "include": ["scores", "players"], "days": 2 }

Sending — Unsubscribe

{ "action": "unsubscribe", "sport": "AFL" }

Receiving — Subscribed

{ "event": "subscribed", "sport": "AFL", "status": "live", "include": ["scores", "players"], "days": 2, "credits_per_push": 1 }

Receiving — Results Update

Sent each time a scrape cycle completes. The data field is identical in structure to the REST response, and the same rule applies: full_time is null until a game is CONCLUDED.

{ "event": "results_update", "sport": "AFL", "timestamp": "2026-07-25T08:31:49.714572", "credits_charged": 1, "data": { "sport": "AFL", "games": [ ... ] } }

Receiving — Error

{ "event": "error", "message": "Invalid include value(s): score. Use one or both of: scores, players" }

Code Examples

import asyncio import websockets import json async def main(): url = "wss://api.rapidoddsapi.com/results-ws?api_key=your_api_key" async with websockets.connect(url) as ws: connected = json.loads(await ws.recv()) print(connected["message"]) await ws.send(json.dumps({ "action": "subscribe", "sport": "AFL", "status": "live", "include": ["scores", "players"] })) sub = json.loads(await ws.recv()) print(f"Subscribed, {sub['credits_per_push']} credit per push") async for message in ws: data = json.loads(message) if data["event"] != "results_update": continue for entry in data["data"]["games"]: game, score = entry["game"], entry["score"] totals = score["totals"] # full_time is null until the game is CONCLUDED, so use # by_period for a live running total. if totals["full_time"] is None: running = sum(p["points"] for p in totals["by_period"]) print(f"{game['home_team']} live, total {running}") else: print(f"{game['home_team']} FINAL, " f"total {totals['full_time']['points']}") asyncio.run(main())

Futures API

Overview

Outright markets that run for a whole season, such as the winner of a competition or an award. They have no fixture, so they use their own endpoint rather than the odds one.

Same API key and the same credit cost as the odds API. REST only, no WebSocket.

Endpoint

GET https://api.rapidoddsapi.com/sports/{sport_id}/futures?api_key={your_api_key}

Parameters

ParameterTypeRequiredDescription
sport_idpathYesSport identifier, for example AFL
api_keyqueryYesYour API key, the same one used for odds
market_typequeryYesOne or more futures market keys, repeated. See the coverage page
bookmakerqueryYesOne or more bookmakers, repeated. The same names the odds endpoint takes
seasonqueryNoSeason year. Omit for the current season

Credits are charged the same way as the odds endpoint: market_types × ceil(bookmakers / 5), and nothing is charged when no markets come back. Error codes are the same too, listed under Error Codes.

Some market keys name the player they exclude, for example brownlow_medal_winner_without_daicos. These are replaced each season rather than carried forward, since the excluded player changes.

Response

The same shape as the odds endpoint without the fixture: bookmakers → markets → outcomes. There is no games array, since a futures market has no game to sit under.

{ "sport": "AFL", "season": 2026, "bookmakers": [ { "name": "Sportsbet", "last_update": "2026-09-06T01:52:50", "markets": [ { "key": "brownlow_medal_winner", "outcomes": [ { "name": "Nick Daicos", "price": 1.01 }, { "name": "Bailey Smith", "price": 51.0 } ] } ] } ] }

To compare prices, group a market's outcomes across bookmakers by outcome name. Names are standardised, so the same runner is spelled identically in every feed.

Outcome qualifiers

In most futures markets the outcome name is the player or club being bet on. Vote total and 3 Vote Games markets work differently: one key covers many players or clubs, so each outcome carries extra fields saying which one it belongs to. All are optional and appear only where they apply.

FieldTypeDescription
pointnumberThe line
player_namestringThe player the outcome refers to
team_namestringThe club the outcome refers to
roundnumberRound number, 0 to 24. 0 is the Opening Round
gamestringFixture, for example "St Kilda v Collingwood"

On these markets name is only "Over" or "Under", so group by the qualifier rather than by name.

{ "key": "brownlow_player_season_votes", "outcomes": [ { "name": "Over", "price": 1.87, "point": 43.5, "player_name": "Nick Daicos" }, { "name": "Under", "price": 1.87, "point": 43.5, "player_name": "Nick Daicos" }, { "name": "Over", "price": 1.90, "point": 31.5, "player_name": "Marcus Bontempelli" } ] }

brownlow_3_votes covers every game of every round under one key. Filter on round and game for the ones you want. Most games include an "Any other player" outcome for the rest of the field.

Code Examples

curl "https://api.rapidoddsapi.com/sports/AFL/futures?\ api_key=your_api_key&\ market_type=brownlow_medal_winner&\ bookmaker=Sportsbet&\ bookmaker=TAB"