This guide shows how an odds API works, how to choose between REST, WebSocket and historical queries, and how to build with it using working Python, Node.js and cURL code. You will also get a production checklist and a framework for evaluating any odds provider.
Key takeaways
- Normalization is the real product. One schema for every bookmaker replaces a parser per source.
- Match the delivery method to the job. REST for pages and jobs, WebSocket for live pricing, historical queries for backtesting.
- Closing lines are the benchmark for judging any pricing model.
- Cache, reconnect and handle suspended markets, or your odds feature will break on match day.
- You can test it free. Orbistats’ Free plan gives you one hour across all 13 sports and every endpoint, with no card.
Table of contents
- What is a sports odds API?
- Odds API vs scores API vs sports data API
- Odds normalization: why it matters
- Betting markets you can pull
- REST, WebSocket or historical: choosing a delivery method
- Make your first odds API request
- Stream live odds over WebSocket
- Convert odds to implied probability
- Historical odds data and backtesting
- How to evaluate an odds API provider
- Who builds on an odds API
- Production checklist
- Free odds API: pricing and access
- FAQ
1. What is a sports odds API?
A sports odds API lets your application request betting prices programmatically instead of copying them by hand or scraping websites. You send an HTTP request or open a socket, and you receive structured JSON describing the match, the market, the outcomes and the current price.
A complete odds API provides four things:
- Pre-match odds: opening and current lines from the moment a fixture is scheduled.
- Live odds: in-play prices that move as the match unfolds.
- Line movement: how a price shifted from open to close, market by market.
- Historical odds data: closing lines and movement from past seasons, for research and models.
Developers use odds APIs to build odds comparison pages, pricing and risk tools, tipster and media sites, fantasy platforms, trading dashboards and machine-learning models. The Orbistats Odds API covers pre-match and live odds across 13 sports and 500+ leagues, with line movement included.
2. Odds API vs scores API vs sports data API
These three get confused constantly, so it helps to separate them:
| API type | Answers the question | Typical data |
| Odds API | What does the market think will happen? | Prices, markets, line movement |
| Live scores API | What is happening right now? | Score, minute, goals, cards, substitutions |
| Sports data API | What is scheduled and what happened? | Fixtures, results, standings, teams |
A score tells you a goal was scored. An odds API tells you how the market reacted. Most serious products combine all three, which is why Orbistats offers the Sports Data API, the Live Scores API and the Odds API on one platform with one key.
3. Odds normalization: why it matters
Every bookmaker describes the same match differently. One calls a market “Match Result”, another “1X2”, another “Full Time Result”. One returns decimal odds (1.91), another American (-110), another fractions (10/11). Team names and outcome order differ too.
Without normalization, you maintain this:
text
Bookmaker A parser
Bookmaker B parser
Bookmaker C parser
Bookmaker D parser
Every format change at any bookmaker becomes your emergency, usually on a busy match day.
Odds normalization maps every source into one consistent schema: one set of market names, one outcome structure, one odds format. You integrate once:
text
Orbistats → Normalized Odds → Your Application
Here is a normalized odds response:
json
{
"match_id": 48213,
"home_team": "Manchester City",
"away_team": "Arsenal",
"bookmaker": "orbistats_consensus",
"market": "1X2",
"odds": { "home": 1.91, "draw": 3.40, "away": 4.20 },
"format": "decimal",
"status": "pre_match",
"updated_at": "2026-08-28T12:04:11Z"
}
What you no longer handle: bookmaker-specific market names, format conversion and outcome mapping. Decimal is the default, and fractional and American formats are available when your audience expects them.
The same schema applies to historical data. The model you backtest on last season’s closing lines reads data in exactly the shape it will see in production, which removes a whole class of training-versus-live bugs.
4. Betting markets you can pull
Depth varies by sport and league, and major competitions carry the full set.
| Market | Also called | Typical use |
| 1X2 | Moneyline (2-way where there is no draw) | Home / draw / away result pricing |
| Spreads | Handicap | Point or goal handicaps for mismatched sides |
| Totals | Over/Under | Combined score or goals above or below a line |
| Player props | Player specials | Individual goals, points, cards and more |
| Futures | Outrights | Season or tournament winners and placements |
Tip: pick your markets deliberately. A fan-facing odds board usually needs moneyline/1X2 and totals. A pricing product adds spreads, props and line movement. Pulling everything “just in case” wastes rate limit and slows your UI.
5. REST, WebSocket or historical: choosing a delivery method
The same normalized schema is available three ways:
| Method | Best for | Latency | How it works |
| REST query | Odds boards, scheduled jobs, lookups | Seconds | Request the current line on demand |
| WebSocket odds feed | Pricing engines, trading desks, live UIs | Under 1 second | Subscribe once, receive every change |
| Historical query | Backtesting and model training | Archival | Closing lines and movement from past seasons |
Choose REST when your refresh interval is longer than a few seconds: pre-match pages, nightly jobs, “price when a user opens a match”.
Choose WebSocket when the price moving is the product. Polling every second wastes requests and still misses changes between polls.
Choose historical when you need proof, which is the subject of section 9.
For event-driven backends there is a fourth option: webhooks. You provide a URL and Orbistats sends a POST when something happens, so you never hold a connection open or poll.
6. Make your first odds API request
Authentication is a bearer token. The quickstart guide covers account and key setup, and the sandbox lets you try calls in the browser first.
cURL
bash
curl "https://api.orbistats.com/v1/football/odds?match_id=48213" \
-H "Authorization: Bearer YOUR_API_KEY"
Python
python
import requests
resp = requests.get(
"https://api.orbistats.com/v1/football/odds",
params={"match_id": 48213},
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=10,
)
resp.raise_for_status()
print(resp.json())
Node.js
javascript
const res = await fetch(
"https://api.orbistats.com/v1/football/odds?match_id=48213",
{ headers: { Authorization: "Bearer YOUR_API_KEY" } }
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
console.log(await res.json());
Always set a timeout and check the status code before parsing. Odds features fail silently when an error body is parsed as if it were odds.
Endpoints follow the pattern /v1/{sport}/..., so the same call works across all 13 sports by changing one path segment. See the sport pages for football, basketball, cricket, tennis and baseball, plus esports, combat sports, volleyball, handball, ice hockey, golf and horse racing.
7. Stream live odds over WebSocket
A live odds API should not make you poll. With the WebSocket API you open one persistent connection and subscribe to what you need.
Connect. The API key goes in the query string, and the connection is rejected if it is missing or invalid:
text
wss://stream.orbistats.com/v1?api_key=YOUR_API_KEY
Subscribe. Send an action, a channel, and optionally a league or match_id:
json
{
"action": "subscribe",
"channel": "odds.movement",
"sport": "football",
"match_id": 48213
}
Python client with reconnect and backoff
python
import asyncio, json, random
import websockets
URL = "wss://stream.orbistats.com/v1?api_key=YOUR_API_KEY"
SUB = {"action": "subscribe", "channel": "odds.movement",
"sport": "football", "match_id": 48213}
async def stream():
delay = 1
while True:
try:
async with websockets.connect(URL, ping_interval=20) as ws:
await ws.send(json.dumps(SUB))
delay = 1
async for raw in ws:
print(json.loads(raw))
except websockets.ConnectionClosed as e:
if e.code == 4001:
raise SystemExit("Invalid or missing API key")
except OSError:
pass
await asyncio.sleep(delay + random.random())
delay = min(delay * 2, 30)
asyncio.run(stream())
JavaScript
javascript
const ws = new WebSocket("wss://stream.orbistats.com/v1?api_key=YOUR_API_KEY");
ws.onopen = () => ws.send(JSON.stringify({
action: "subscribe", channel: "odds.movement",
sport: "football", match_id: 48213,
}));
ws.onmessage = (e) => console.log(JSON.parse(e.data));
ws.onclose = (e) => console.warn("closed", e.code); // reconnect with backoff
Security: a key in browser code is visible to anyone. For public sites, route the stream through your backend or use widgets, and keep your key server-side.
Close codes to handle
| Code | Meaning | Action |
| 1000 | Normal closure | Reconnect when ready |
| 4001 | Invalid or missing key | Stop and fix the key |
| 4008 | Rate limit for your plan | Back off, check connection limits |
| 1006 | Abnormal closure | Reconnect with exponential backoff |
Heartbeats (ping/pong) keep connections alive and detectable. Connection limits depend on plan (Free allows 2 concurrent connections, Starter 10), so reuse one socket and subscribe to many channels rather than opening one socket per match.
8. Convert odds to implied probability
Raw odds are prices. Most analysis starts by converting them to implied probability:
text
implied probability = 1 / decimal odds
Using 1.91 / 3.40 / 4.20:
python
odds = {"home": 1.91, "draw": 3.40, "away": 4.20}
implied = {k: 1 / v for k, v in odds.items()}
total = sum(implied.values())
print(f"Overround: {(total - 1) * 100:.1f}%")
fair = {k: p / total for k, p in implied.items()}
for outcome, p in fair.items():
print(f"{outcome}: {p:.1%}")
text
Overround: 11.3%
home: 47.0%
draw: 26.4%
away: 21.4%
The raw probabilities add up to more than 100%. That excess is the bookmaker’s margin, called the overround. Dividing by the total is the simplest way to remove it. More advanced margin-removal methods exist, but this is a sound starting point.
Format conversion helpers
python
from fractions import Fraction
def decimal_to_american(d):
return round((d - 1) * 100) if d >= 2 else round(-100 / (d - 1))
def decimal_to_fractional(d):
f = Fraction(d - 1).limit_denominator(20)
return f"{f.numerator}/{f.denominator}"
print(decimal_to_american(1.91)) # -110
print(decimal_to_fractional(3.40)) # 12/5
The API can return decimal, fractional or American directly, so you mainly need these for validation. For plain-English definitions, see the glossary, and for quick checks use the odds converter tools.
9. Historical odds data and backtesting
A backtest is how you show that a model works rather than claim it. The Historical Sports Data API provides closing lines and full movement history across multiple seasons in the same schema as the live feed.
Why closing lines matter. The closing line absorbs injury news, lineups and sharp money right up to kick-off, so it is widely treated as the market’s most informed price. A model that consistently beats the closing line has meaningful evidence behind it. One that only beats the opening line does not.
A minimal backtest skeleton (the DataFrame columns are illustrative):
python
import pandas as pd
# df: match_id, home_odds, draw_odds, away_odds, result ("H"/"D"/"A"),
# model_home, model_draw, model_away
def kelly(p, odds):
b = odds - 1
return max((b * p - (1 - p)) / b, 0)
bankroll = 1000.0
for r in df.itertuples():
for side, odds, p in [("H", r.home_odds, r.model_home),
("D", r.draw_odds, r.model_draw),
("A", r.away_odds, r.model_away)]:
if p * odds - 1 > 0.03: # require a 3%+ edge
stake = bankroll * kelly(p, odds) * 0.25 # quarter Kelly
bankroll += stake * (odds - 1) if r.result == side else -stake
print(f"Final bankroll: {bankroll:.2f}")
Three rules for honest backtests
- Hold out the most recent season completely. Never train and test on the same data.
- Avoid look-ahead bias. Use only information that existed before each match.
- Do not treat a strong backtest as a guarantee. Markets adapt and edges decay.
10. How to evaluate an odds API provider
Whichever provider you consider, test these before you commit:
| Question | What to check |
| Is the schema normalized? | One market name and outcome structure across sources, not raw bookmaker passthrough |
| Are live odds truly live? | WebSocket delivery with measured latency, not polling dressed up as streaming |
| Is history in the same schema? | Closing lines and movement that match the live format |
| Which markets and sports? | 1X2/moneyline, spreads, totals, props, futures, and your specific leagues |
| How does it fail? | Documented close codes, rate-limit behaviour, public status page |
| What does a trial cost? | A free tier you can test with real requests before paying |
| Is pricing transparent? | Numbers visible without a sales call |
| Is there a reliability commitment? | Uptime SLA and redundant sourcing on paid plans |
The most reliable test is also the cheapest one: make real requests during a live match and compare the data to what you need. Orbistats publishes a status page and a comparison section so you can run this check yourself.
11. Who builds on an odds API
| If you are building… | You mostly need | Start with |
| A sportsbook or pricing tool | Normalized multi-market odds, props, results | REST + WebSocket |
| A trading desk or arbitrage monitor | Sub-second line movement | WebSocket |
| A media or tipster site | Odds comparison without a custom UI | Widgets + REST |
| A fantasy or analytics platform | Player data plus history | Statistics API + historical |
| A data science or ML project | Clean schema, closing lines, bulk history | Historical + REST |
Dedicated pages cover sportsbooks and trading, trading desks, media and publishers and analytics and data science.
12. Production checklist
- Cache REST responses. Pre-match odds rarely change every second, so a 10 to 30 second cache cuts requests and speeds up pages.
- Handle suspended and missing markets. Show “suspended”, never a stale number.
- Display timestamps. Show updated_at so users know how fresh a price is.
- Reconnect with exponential backoff and jitter, and resubscribe after every reconnect.
- Keep API keys on the server.
- Design for rate limits. Handle 4008, not only the happy path.
- Log raw payloads briefly. When a user reports a wrong price, you need evidence.
- Pin to /v1/ and parse defensively, since non-breaking fields may be added.
- Add the status page to your runbook to separate your incidents from provider incidents.
- Follow local gambling and advertising rules for consumer-facing products, including responsible-gambling messaging where required.
13. Free odds API: pricing and access
Every Orbistats plan includes all sports and all endpoints. Only the length of your access window changes:
| Plan | Price | Access window |
| Free | €0, no card | 1 hour |
| Starter | €19 | 24 hours |
| Growth | €79 | 5 days |
| Unlimited | €1,999/month | 30 days, unlimited usage |
Your timer starts only when you open your activation link, so you choose when to begin. The free hour suits a quick technical test: inspect the schema, try a WebSocket subscription and decide before you spend anything. Check the live pricing page for current plans.
14. FAQ
Is there a free odds API?
Yes. Orbistats’ Free plan gives one hour of access to every sport and endpoint, with no card required, counted from when you open your activation link.
What is a normalized odds API?
It is an odds API that maps every bookmaker into one schema, with the same market names, outcome structure and odds format, so you integrate once instead of writing a parser per bookmaker.
How fast is a live odds API?
Orbistats delivers live odds in under a second over WebSocket, while live scores and match events stream at sub-50ms. REST responses arrive in seconds and suit pages and scheduled jobs.
Can I get historical odds data for backtesting?
Yes. Opening lines, closing lines and line movement are available, and multi-season history is accessible through the Historical Sports Data API in the same schema as live data.
Which sports and markets does it cover?
Thirteen sports: football, basketball, American football, cricket, tennis, baseball, esports, combat sports, volleyball, handball, ice hockey, golf and horse racing. Markets include 1X2/moneyline, spreads, totals, player props and futures, with depth varying by league.
Should I use REST or WebSocket for odds?
Use REST for pages, jobs and on-demand lookups. Use WebSocket when you need every price change as it happens, such as pricing engines, trading dashboards and live odds boards.
How do I get an API key?
Request access, wait for your activation email, and open the link when you are ready to start. The quickstart guide covers every step.
Related guides
(Add these links as each supporting post goes live. Each should link back here using the anchor text “sports odds API guide”.)
- How odds normalization works across bookmakers → /blog/odds-normalization-explained/
- REST vs WebSocket for live odds → /blog/rest-vs-websocket-odds/
- Implied probability and overround, explained → /blog/implied-probability-overround/
- How to backtest a betting model with closing odds → /blog/backtest-odds-model-closing-lines/
- Understanding line movement → /blog/line-movement-explained/
- Building an odds comparison widget → /blog/odds-comparison-widget/
Start building
Make your first request in minutes with the quickstart guide, read the Odds API reference, go deeper on streaming in the WebSocket API docs, or explore historical odds data. Compare plans on the pricing page.
Ready to build? Request access, open your activation link when you are ready, and send your first normalized odds request.
Schema markup (Article + FAQPage + Breadcrumb)
html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Article",
"headline": "Sports Odds API for Developers: Normalized Odds, Live WebSocket Feeds & Historical Data (2026 Guide)",
"description": "Sports odds API explained: normalized odds, live WebSocket feeds, historical closing lines, code examples in Python & Node.js, plus a free tier.",
"image": "https://orbistats.com/assets/blog/sports-odds-api-guide.webp",
"author": { "@type": "Organization", "name": "Orbistats" },
"publisher": { "@type": "Organization", "name": "Orbistats", "url": "https://orbistats.com/" },
"datePublished": "2026-10-10",
"dateModified": "2026-10-10",
"mainEntityOfPage": "https://orbistats.com/blog/sports-odds-api-guide/"
},
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://orbistats.com/" },
{ "@type": "ListItem", "position": 2, "name": "Blog", "item": "https://orbistats.com/blog/" },
{ "@type": "ListItem", "position": 3, "name": "Sports Odds API Guide" }
]
},
{
"@type": "FAQPage",
"mainEntity": [
{ "@type": "Question", "name": "Is there a free odds API?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. Orbistats' Free plan gives one hour of access to every sport and endpoint, with no card required, counted from when you open your activation link." } },
{ "@type": "Question", "name": "What is a normalized odds API?",
"acceptedAnswer": { "@type": "Answer", "text": "It is an odds API that maps every bookmaker into one schema, with the same market names, outcome structure and odds format, so you integrate once instead of writing a parser per bookmaker." } },
{ "@type": "Question", "name": "How fast is a live odds API?",
"acceptedAnswer": { "@type": "Answer", "text": "Orbistats delivers live odds in under a second over WebSocket, while live scores and match events stream at sub-50ms." } },
{ "@type": "Question", "name": "Can I get historical odds data for backtesting?",
"acceptedAnswer": { "@type": "Answer", "text": "Yes. Opening lines, closing lines and line movement are available, and multi-season history is accessible in the same schema as live data." } },
{ "@type": "Question", "name": "Should I use REST or WebSocket for odds?",
"acceptedAnswer": { "@type": "Answer", "text": "Use REST for pages, jobs and on-demand lookups. Use WebSocket when you need every price change as it happens, such as pricing engines and live odds boards." } }
]
}
]
}
</script>
