FIFA WORLD CUP 2026
Group AMexico·Czechia·South Africa·South KoreaGroup BBosnia & Herzegovina·Canada·Qatar·SwitzerlandGroup CBrazil·Haiti·Morocco·ScotlandGroup DAustralia·Paraguay·Türkiye·USAGroup ECuraçao·Ecuador·Germany·Côte d'IvoireGroup FJapan·Netherlands·Sweden·TunisiaGroup GBelgium·Egypt·Iran·New ZealandGroup HCabo Verde·Saudi Arabia·Spain·UruguayGroup IFrance·Iraq·Norway·SenegalGroup JAlgeria·Argentina·Austria·JordanGroup KColombia·DR Congo·Portugal·UzbekistanGroup LCroatia·England·Ghana·Panama Group AMexico·Czechia·South Africa·South KoreaGroup BBosnia & Herzegovina·Canada·Qatar·SwitzerlandGroup CBrazil·Haiti·Morocco·ScotlandGroup DAustralia·Paraguay·Türkiye·USAGroup ECuraçao·Ecuador·Germany·Côte d'IvoireGroup FJapan·Netherlands·Sweden·TunisiaGroup GBelgium·Egypt·Iran·New ZealandGroup HCabo Verde·Saudi Arabia·Spain·UruguayGroup IFrance·Iraq·Norway·SenegalGroup JAlgeria·Argentina·Austria·JordanGroup KColombia·DR Congo·Portugal·UzbekistanGroup LCroatia·England·Ghana·Panama
Open full hub →
Growing Discord community — direct access to the developer, live coverage & picks. Join the Discord Join now →
Tournaments Rankings Matches Predictions ⚡ Get the Sports Addon
Tennis API · v2

Tennis data API.

REST endpoints for ATP/WTA matches, tournaments, players, rankings, and ML predictions, plus an MCP server for Claude/ChatGPT/Gemini. Sports Addon · $5/mo

Quickstart

Three steps to your first request:

  1. Register at sports.bzzoiro.com/register/ — free account.
  2. Subscribe to the Sports Addon ($5/mo) to unlock the tennis API.
  3. Copy your API token from the dashboard and send it as Authorization: Token YOUR_TOKEN.
# Live tennis matches with set-by-set scores curl -H "Authorization: Token YOUR_TOKEN" \ "https://sports.bzzoiro.com/tennis/api/v2/matches/live/"

Authentication

Pass your token on every request via the Authorization header:

Authorization: Token YOUR_TOKEN

Tokens never expire. Keep them secret. If a token leaks, regenerate it from the dashboard.

Base URL

Base URL
https://sports.bzzoiro.com/tennis
Alias (301 redirect)
https://tennis.bzzoiro.com → /tennis
API prefix
/tennis/api/v2/

Pagination

List endpoints use limit + offset query parameters and return:

{ "count": 408, "next": "https://sports.bzzoiro.com/tennis/api/v2/tournaments/?limit=50&offset=50", "previous": null, "results": [/* … */] }
  • limit — page size. Default 50, max 200 (rankings: max 500).
  • offset — number of items to skip. Default 0.

Tournaments

Active tournaments — every tier from Grand Slams down to Challengers and ITFs.

GET /api/v2/tournaments/ List

Paginated list of tennis tournaments. Active-only by default.

ParameterTypeDescription
circuitstringATP or WTA (case-insensitive)
categorystringgrand_slam, masters_1000, atp_500, atp_250, wta_1000, wta_500, wta_250
surfacestringhard, clay, grass, carpet
include_inactivebooleanInclude inactive tournaments. Default false.
limit / offsetintPagination (default 50, max 200)
GET /api/v2/tournaments/{id}/ Detail

Full detail for a single tournament. Cached 5 min.

Players

Player profiles across both tours, with current ranking and biographical data.

GET /api/v2/players/ List

Paginated player list. Each player includes their current ranking.

ParameterTypeDescription
genderstringM or F
countrystringISO 3166-1 alpha-2 country code (e.g. ES, US)
searchstringCase-insensitive name fragment
limit / offsetintPagination (default 50, max 200)
# Top male Spanish players curl -H "Authorization: Token YOUR_TOKEN" \ "https://sports.bzzoiro.com/tennis/api/v2/players/?gender=M&country=ES&limit=10"
GET /api/v2/players/{id}/ Detail

Full profile: name, country, height, weight, plays (right/left), turned-pro year, current ranking. Cached 5 min.

Matches

Tennis matches with set-by-set scores, serve stats, and live snapshots.

GET /api/v2/matches/ List

Paginated list. With no date filter, defaults to the next 7 days. Every match includes decimal match-winner odds odds_player1 and odds_player2 (null when no price is available).

ParameterTypeDescription
date_from / date_todateYYYY-MM-DD bounds
tournamentint / strTournament ID or name fragment
playerint / strPlayer ID or name fragment
statusstringscheduled, live, interrupted (rain delay / mid-match pause — partial sets retained), finished, cancelled, postponed, walkover, retired
limit / offsetintPagination
GET /api/v2/matches/live/ Live

Only currently in-progress matches with the live snapshot: current_set, current_game_p1, current_game_p2, current_point (e.g. "40-30"), is_serving_p1. Cached 30 seconds.

GET /api/v2/matches/{id}/ Detail

Full match details including sets_detail (per-set breakdown), live state (if in-progress), per-player serve stats: aces, double faults, first/second serve %, and decimal match-winner odds (odds_player1 / odds_player2). See also: /{id}/odds/ for per-bookmaker prices and /{id}/h2h/ for head-to-head records.

{ "id": 33405, "tournament": { "name": "ATP Rome Masters", "surface": "clay" }, "player1": { "name": "Carlos Alcaraz", "current_ranking": { "position": 2, "type": "ATP" } }, "player2": { "name": "Jannik Sinner", "current_ranking": { "position": 1, "type": "ATP" } }, "status": "finished", "player1_sets": 2, "player2_sets": 1, "sets_detail": [{"p1":6,"p2":4}, {"p1":4,"p2":6}, {"p1":7,"p2":5}], "p1_aces": 12, "p2_aces": 8, "p1_first_serve_pct": 68, "p2_first_serve_pct": 71, "odds_player1": 1.44, "odds_player2": 2.75 }
GET /api/v2/matches/{id}/odds/ Odds

Per-bookmaker match-winner odds for a single match. Returns decimal prices from 14+ bookmakers with price movement. Prices are refreshed every 3 hours across all active ATP, WTA, Challenger and WTA 125K events. When multi-bookmaker data is not yet available for a match, falls back to a single consensus price.

{ "match_id": 36551, "match_date": "2026-06-02T18:15:00Z", "player1_name": "Jakub Menšik", "player2_name": "Joao Fonseca", "bookmakers_count": 14, "source": "multi", "bookmakers": [ { "bookmaker": "Bet365", "bookmaker_slug": "bet365", "odds_player1": 2.375, "odds_player2": 1.571, "movement_player1": "SHORTENING", // SHORTENING | DRIFTING | STABLE | null "movement_player2": null, "updated_at": "2026-06-02T13:58:00Z" } ] }
GET /api/v2/matches/{id}/h2h/ H2H

Head-to-head record between the two players of a match, plus last 5 finished matches for each player. Returns overall wins, wins by surface (hard/clay/grass), and recent match history.

{ "match_id": 36551, "player1": { "name": "Jakub Menšik", /* … */ }, "player2": { "name": "Joao Fonseca", /* … */ }, "h2h": { "total_matches": 3, "player1_wins": 2, "player2_wins": 1, "by_surface": { "clay": { "total": 1, "player1_wins": 1 }, "hard": { "total": 2, "player1_wins": 1 } } }, "player1_last5": [/* last 5 finished matches */], "player2_last5": [/* last 5 finished matches */] }

Predictions

ML predictions calibrated against historical accuracy. XGBoost backbone.

GET /api/v2/predictions/ List

Paginated predictions. Defaults to upcoming.

ParameterTypeDescription
upcomingbooleanDefault true. Set false for past predictions.
date_from / date_todateYYYY-MM-DD bounds
limit / offsetintPagination
GET /api/v2/predictions/{id}/ Detail

Each prediction returns:

  • prob_player1_wins, prob_player2_wins — match winner probabilities (0–100)
  • predicted_winner — 1 or 2
  • confidence — model self-rated confidence (0–100)
  • expected_total_sets, prob_over_2_5_sets
  • expected_total_games, prob_over_20_5_games, prob_over_21_5_games, prob_over_22_5_games
  • prob_player1_wins_first_set
  • actual_winner, was_winner_correct — populated after the match ends

Rankings

ATP & WTA weekly snapshots. Each row carries the previous week's position + points and the player's career best.

GET /api/v2/rankings/ List

Latest snapshot by default. Pass date for a historical snapshot.

ParameterTypeDescription
typestringATP (default) or WTA
datedateYYYY-MM-DD snapshot date
limit / offsetintPagination (max 500)
# Current ATP top 10 curl -H "Authorization: Token YOUR_TOKEN" \ "https://sports.bzzoiro.com/tennis/api/v2/rankings/?type=ATP&limit=10"

MCP server

The tennis MCP endpoint exposes 7 typed tools to LLM clients (Claude Desktop, ChatGPT, Cursor, Gemini). Drop the config below into your client and ask it questions in plain English.

📘 Full setup guide: step-by-step instructions for Claude Desktop, Cursor, Gemini CLI, VS Code, and a tools reference at /tennis/mcp-info/

POST /mcp/ JSON-RPC 2.0

Streamable HTTP transport. Auth via the same Authorization: Token header (or ?token= query param).

Available tools:

  • list_tournaments — filter by circuit/category
  • list_players — filter by gender/country
  • search_players — by name fragment
  • list_matches — by status, date window
  • get_match — match details by ID
  • get_predictions — upcoming match predictions
  • get_rankings — top N ATP or WTA

Claude Desktop config

// claude_desktop_config.json { "mcpServers": { "bzzoiro-tennis": { "url": "https://sports.bzzoiro.com/tennis/mcp/", "transport": "http", "headers": { "Authorization": "Token YOUR_TOKEN" } } } }

Static images

Player photos, served as PNG via the BSD image proxy. Public endpoint — no auth required, drop straight into <img src=> tags. Cached up to 1 year on the CDN.

GET/img/tennis-player/{player_id}/PNG

Returns the player headshot. player_id is the id field from any tennis API response.

// HTML usage <img src="https://sports.bzzoiro.com/img/tennis-player/14882/" alt="Carlos Alcaraz" /> # curl — saves PNG to disk curl -o alcaraz.png "https://sports.bzzoiro.com/img/tennis-player/14882/"

Returns 404 when the upstream has no image for that player. Sister endpoints exist for the other sports: /img/csgo-team/, /img/csgo-player/, /img/csgo-tournament/, /img/darts-player/, /img/hockey-team/, /img/hockey-league/.

Examples

Today's live matches with set scores

import requests H = {"Authorization": "Token YOUR_TOKEN"} r = requests.get("https://sports.bzzoiro.com/tennis/api/v2/matches/live/", headers=H).json() for m in r: sets = m["sets_detail"] or [] score = " ".join(f"{s['p1']}-{s['p2']}" for s in sets) print(f"{m['player1']['name']} vs {m['player2']['name']} — {score}")

Predictions for an upcoming tournament

const H = { Authorization: "Token YOUR_TOKEN" }; // Get all upcoming Wimbledon matches const matches = await fetch("https://sports.bzzoiro.com/tennis/api/v2/matches/?tournament=Wimbledon&status=scheduled", { headers: H }) .then(r => r.json()); for (const m of matches.results) { console.log(m.player1.name, "vs", m.player2.name); }

Live WebSocket

Tennis matches are available over the same wss://sports.bzzoiro.com/ws/live/ endpoint as football. Add "sport": "tennis" to your subscribe message. Requires the Live WebSocket addon ($3/mo) — separate from the Sports Pack.

Subscribe

// Subscribe to a live tennis match ws.send(JSON.stringify({ action: "subscribe", event_id: 36835, // TennisMatch.id from the REST API sport: "tennis" }));

Frames you receive

event every ~5s on change
{
  "type": "event",
  "sport": "tennis",
  "home": {"name": "Djokovic, N."},
  "away": {"name": "Alcaraz, C."},
  "score": {
    "sets": [[6,3],[4,6],[2,1]],
    "home_sets": 2, "away_sets": 1,
    "point": "40-15",
    "server": "home"
  },
  "stats": {
    "home": {"aces": 8, "first_serve_pct": 63.4},
    "away": { ... }
  }
}
score real-time point update
{
  "type": "score",
  "event_id": 36835,
  "uts": 1780516577,
  "sets": [[6,3],[4,6],[2,1]],
  "set": 3,
  "game": "2-1",
  "point": "40-15",
  "server": "home"
}

Polled from upstream every 5 seconds. Only pushed when a new point is registered.

Full example (Python)

import json, websocket ws = websocket.create_connection( "wss://sports.bzzoiro.com/ws/live/?token=YOUR_TOKEN" ) ws.send(json.dumps({ "action": "subscribe", "event_id": 36835, "sport": "tennis" })) while True: f = json.loads(ws.recv()) if f["type"] == "score": sets = f["sets"] print(f"Sets: {sets} | Point: {f['point']} | Server: {f['server']}") elif f["type"] == "event": sc = f["score"] print(f"Aces: {f['stats']['home']['aces']}/{f['stats']['away']['aces']}")

See the WebSocket protocol docs for authentication, error codes and the full frame reference.

Need help? Email bzzoiro@proton.me. Bug reports welcome.