bet365 odds feed
Every bet365 price for 24 sports, pre-match and in-play, as one JSON feed. Free and no key needed
What you get
GET /odds gives you the whole board, every match bet365 lists with every market it prices, from the match winner to handicaps, totals, correct score, corners, cards and player props. Pick a sport with sport= or grab them all with sport=all, and cut it down with groups= (market ids) or in_play=1
curl --compressed "https://moviodds.com/odds?sport=soccer&groups=40"
Big boards are big so ask for gzip (--compressed in curl, Python requests does it by itself)
The JSON
Each match has its teams, league, kickoff and a quotes list, one entry per price:
{
"id": "202905871",
"home": "Lee Man FC",
"away": "Kitchee",
"league": "Hong Kong Premier League",
"kickoff_utc": "2026-10-19T10:30:00Z",
"sport": "soccer",
"quotes": [
{"group_id": "40", "group_name": "Full Time Result", "sel_id": "...", "label": "Lee Man FC", "odds_dec": 2.3, "odds_frac": "13/10"},
{"group_id": "40", "group_name": "Full Time Result", "sel_id": "...", "label": "Draw", "odds_dec": 3.9, "odds_frac": "29/10"},
…
]
}
Market and selection ids stay the same across matches so you can filter and store on them. Every market id is on the market list
CSV and XML
We serve JSON only, there's no XML endpoint. If your tool wants CSV or XML (Excel, an old bookmaker system, a spreadsheet) it's a few lines of Python:
import csv
import requests
import xml.etree.ElementTree as ET
r = requests.get("https://moviodds.com/odds", params={"sport": "soccer", "groups": "40"}, timeout=60)
r.raise_for_status()
matches = r.json()["matches"]
# CSV, one row per price
with open("bet365_odds.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["match_id", "league", "home", "away", "kickoff_utc", "market", "selection", "odds"])
for m in matches:
for q in m["quotes"]:
w.writerow([m["id"], m["league"], m["home"], m["away"], m.get("kickoff_utc"),
q["group_name"], q.get("label") or q.get("side"), q["odds_dec"]])
# XML, one <match> with its <price> elements
root = ET.Element("odds", source="bet365")
for m in matches:
e = ET.SubElement(root, "match", id=m["id"], league=m["league"], home=m["home"], away=m["away"])
for q in m["quotes"]:
ET.SubElement(e, "price", market=q["group_name"], selection=q.get("label") or q.get("side") or "",
odds=str(q["odds_dec"]))
ET.ElementTree(root).write("bet365_odds.xml", encoding="utf-8", xml_declaration=True)
print(len(matches), "matches saved")
751 matches saved
You end up with bet365_odds.csv, one row per price, and bet365_odds.xml. Run it on a schedule and you have a bet365 XML feed
Keep it live
Polling works but the WebSocket is better for anything live. Connect to wss://moviodds.com/ws, send {"op":"subscribe"} and you get every price change within about a second of bet365. See the WebSocket guide
FAQ
Is the bet365 odds feed free?
Yes, no sign-up and no key. /odds allows 300 requests a minute per IP
Do you have a bet365 XML feed?
The feed is JSON. The Python above turns it into XML or CSV, run it on a schedule if you need a file feed
Which sports are in the feed?
24 sports, soccer, tennis, basketball, American football, ice hockey, baseball, cricket, rugby, esports and more. See all sports
Is it live?
Yes, pre-match and in-play. In-play prices come over the WebSocket within about a second of bet365 changing them
Are these really bet365 odds?
Yes, only bet365's own prices. MoviOdds is an independent project and is not affiliated with bet365
More: live WebSocket · historical odds · all 24 sports · Python · API docs