Tournaments are per event here, so discover the current draw from the tournament list, not a fixed id.
Measured live at 2026-09-09 21:50 UTC. This page is regenerated from the API, so the numbers move.
Quick start: Tennis fixtures and prices
Copy it as it stands. It was run against the live API before this page rendered.
import datetime as dt
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE_URL = "https://api.oddspapi.io/v4"
def get(path, params):
"""The key is the query parameter apiKey, never a header.
Read the status code first: a 429 body is valid JSON too."""
query = dict(params, apiKey=API_KEY)
for attempt in range(3):
response = requests.get(f"{BASE_URL}/{path}", params=query, timeout=180)
if response.status_code == 429:
time.sleep(5 * (attempt + 1))
continue
if response.status_code == 404:
return None
response.raise_for_status()
return response.json()
raise RuntimeError("rate limited on /" + path)
# Fixtures for Tennis. sportId 12 is the only filter here,
# so the from and to range has to stay under 10 days. Adding tournamentId
# lifts that cap.
fixtures = get("fixtures", {
"sportId": 12,
"from": "2026-09-09",
"to": "2026-09-18",
}) or []
print(len(fixtures), "fixtures in the window")
# The same bet ships under more than one market id on several sports, so
# resolve the result market from /v4/markets by name and read every id that
# maps to it. Reading one id drops part of the board.
MAIN_MARKET_IDS = ["121"]
# hasOdds is a flag, not a depth signal, and it is false on every finished
# fixture. Boards open on their own clock, so the fixtures nearest kick-off
# carry the prices, and a fixture already in play often has the result market
# suspended. Read the ones still ahead of us, soonest first.
now = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z")
upcoming = sorted(
(f for f in fixtures if f["hasOdds"] and f["startTime"] > now),
key=lambda f: f["startTime"],
)
for fixture in upcoming[:10]:
odds = get("odds", {
"fixtureId": fixture["fixtureId"],
"bookmakers": "pinnacle,bet365,draftkings",
})
boards = (odds or {}).get("bookmakerOdds") or {}
printed = False
for slug, board in boards.items():
for market_id in MAIN_MARKET_IDS:
market = board["markets"].get(market_id)
if not market:
continue
# players is keyed "0" on a match market. On a player prop it is
# keyed by player id, so never hardcode "0" outside this case.
prices = {
outcome_id: leg["players"]["0"]["price"]
for outcome_id, leg in market["outcomes"].items()
if leg["players"]["0"]["active"]
}
if prices:
print(fixture["participant1Name"], "v",
fixture["participant2Name"], slug, market_id, prices)
printed = True
if printed:
break
time.sleep(1.0)
Run against the live API at 2026-09-09 21:51 UTC. First line of output: 1089 fixtures in the window
Tournaments carrying the most fixtures
Read from /v4/tournaments?sportId=12 at 2026-09-09 21:50 UTC. Pass the id straight to /v4/fixtures.
| Tournament | Category | tournamentId | Fixtures scheduled |
|---|---|---|---|
| US Open Men Singles | ATP | 2591 |
83 |
| US Open Women Singles | WTA | 2595 |
83 |
| UTR PTT Waco Men 03 | UTR Men | 48209 |
64 |
| UTR PTT Waco Women 03 | UTR Women | 49440 |
54 |
| UTR PTT Skopje Men 04 | UTR Men | 48559 |
32 |
| UTR PTT Skopje Women 04 | UTR Women | 49031 |
32 |
| UTR PTT Frankfurt Men 01 | UTR Men | 52844 |
32 |
| UTR PTT Miami Men 01 | UTR Men | 52846 |
32 |
| UTR PTT Frankfurt Women 01 | UTR Women | 52848 |
32 |
| UTR PTT Miami Women 01 | UTR Women | 52850 |
32 |
Bookmakers pricing Tennis
110 bookmakers quoted, 29 independent prices, on Mattioli, Francesca v Kennedy, Joanna, measured 2026-09-09 21:18 UTC. Identical price tuples are grouped before the second count, because distinct slugs publish the same price and cloneOf does not track that.
and 11 more on https://oddspapi.io/sportsbooks
Counted from one /v4/odds call with no bookmaker filter, on the main result market for the sport. Regional feeds of one brand are shown once and link to that brand.
Market families
Read off /v4/markets and filtered on the sport. A family that carries many market ids is a ladder, with one id per line, so resolve a market on its name and collect every id that maps to it.
| Market family | Market ids | Type |
|---|---|---|
| Winner | 1 | Match market |
| Total Sets Over Under | 3 | Match market |
| Set Handicap | 4 | Match market |
| Correct Score | 2 | Match market |
| Correct Score Fifth Set | 1 | Match market |
| Correct Score Fifth Set After Four Games | 1 | Match market |
| Correct Score Fifth Set After Six Games | 1 | Match market |
| Correct Score Fifth Set After Two Games | 1 | Match market |
| Correct Score First Set | 1 | Match market |
| Correct Score First Set After Four Games | 1 | Match market |
| Correct Score First Set After Six Games | 1 | Match market |
| Correct Score First Set After Two Games | 1 | Match market |
Read the study
Tennis Odds API: live ATP and WTA. The measurements behind this page, written up in full.
Other sports
Questions developers ask
Which sportId do I pass for Tennis?
12. Read the full list from /v4/sports; the ids are stable and the slug is on the same object.
How many bookmakers price a Tennis fixture?
110 bookmakers quoted Mattioli, Francesca v Kennedy, Joanna, and those group into 29 independent prices once identical price tuples are collapsed, measured 2026-09-09 21:18 UTC. Group the tuples before you average, because distinct slugs publish the same price.
How do I find the live tournaments for Tennis?
Call /v4/tournaments?sportId=12 and keep the rows where futureFixtures is above one. 29 rows passed that test at 2026-09-09 21:50 UTC. Confirm a circuit against /v4/fixtures grouped on tournamentId, which is the reliable way to discover one.
Pull Tennis prices yourself
One key, one fixture id, one JSON shape across every sport on this site. The free tier runs on the same endpoints as the paid one.
