Tennis
Tennis API for Scores, Odds & AI Agents
One event graph for ATP/WTA tennis — set-by-set schedules and live scores, moneyline / games-handicap / total-games odds across nine books, rankings-and-serve-rate /stats, and a market-anchored intelligence Price plane agents can reason over.
Available data
Everything below filters on sport=tennis; narrow to a tour with league=atp or league=wta. Discover players ranked with GET /v1/players?sport=tennis&ranked=true.
| Layer | Endpoint | Tennis notes |
|---|---|---|
| Schedule / board | GET /v1/events | sport=tennis; league=atp/wta; status, date, include_odds / include_scores |
| Live score | GET /v1/events/{id}/score · SSE | Set-by-set sets_won / sets[]; clock is null |
| Odds | GET …/odds · …/odds/history | Moneyline (h2h), games handicap (spreads), total games (totals) · 9 books |
| Period odds | GET …/period-odds | First-set (S1) games handicap / total |
| Player props | GET …/player-props | Not available for tennis |
| Team totals | GET …/team-props | Not available — individual sport |
| Splits | GET …/splits | Not available for tennis |
| Stats (Data) | GET …/stats | Rankings, form, surface form, H2H, career serve/return rates — singles main-draw only |
| Intelligence | GET …/intelligence | Stage 1: moneyline + games handicap Price overlay; totals not yet published |
| EV scan (Beta) | GET /v1/intelligence/ev | Pregame moneyline/spreads +EV list (market=h2h|spreads; totals returns 400). MCP list_ev |
Schedules
List the draw with GET /v1/events?sport=tennis. Add league=atp or league=wta to narrow by tour. Filter by status (scheduled / inprogress / final), date or from/to (max 90 days per request), and paginate with after_id / limit.
Retirements and walkovers surface via event status/result fields rather than a missing row.
Scores
Tennis score payloads are set-based: sets_won (player_1/player_2), winner_role once decided, and a sets[] array with player_1_games / player_2_games / tiebreak_score per set. Game clock is null (not applicable to tennis).
- GET /v1/events?sport=tennis&status=inprogress&include_scores=true — live board
- GET /v1/events/{id}/score — lightweight poll
- GET /v1/events/{id}/stream — SSE event: score or signed webhooks
Cloneable pattern: live scoreboard use case (keep keys server-side).
Odds
Tennis markets are two-way: moneyline (h2h), games handicap (spreads), and total games (totals). American odds integers; point is null on moneyline sides. Default single-book call is Pinnacle; bookmaker=all is the same 1 credit.
Tennis is the one grading gap on final events: game-count isn't tracked through settlement, so result/close are only populated for moneyline — spreads/totals stay unresolved after the match ends. GET /v1/events/{id}/odds/history still returns recorded price/point moves for every market.
Full bookmaker list: Sports Odds API.
Stats (Data layer)
GET /v1/events/{id}/stats returns a tennis-native payload, player-keyed (player_1/player_2) rather than home/away:
- Current ATP/WTA singles ranking + ranking_points · rest_days since each player's last completed match
- recent_form (all surfaces) and surface_form (this match's surface) — trailing results, sets for/against
- record — win/loss over a trailing lookback window from our own event history
- career_rates — career hold %, break-point conversion/save %, first/second-serve-won % when the vendor has persisted match-stats
- career_surface — trailing win/loss on this match's surface (hard/clay/grass/indoor hard/carpet)
- head_to_head — up to 10 prior meetings with set scores and surface
No confidence, narrative, or odds inside /stats. available is false unless draw_type is singles and both players resolve (doubles and qualifying are out of scope today). Pair with /intelligence when you want the market Price plane on the same ID. Fields: API reference — stats.
Intelligence
Tennis /intelligence is Stage 1: a customer surface for moneyline and games handicap only. Framework plumbing (phase, blend_w, p_model, p_market, sufficiency, drivers, alignment, model_version) is omitted. probability / fair_price are a vig-stripped market reference — not a model pick — and edge / tier / has_recommend stay null/false until Edge clears.
- fair — sharp-consensus fair (single-book Pinnacle reference is valid for tennis; no second sharp book)
- edges_by_book — price gap vs fair.probability per soft book
- best — highest-gap book/price for line-shopping (Tier C informational)
- ev (Beta) — the same positive gap packaged as ev_pct + Kelly, on moneyline and spreads. Scan with GET /v1/intelligence/ev?market=h2h or spreads (totals returns 400).
Tokens: ML_P1/ML_P2, SPREAD_P1/SPREAD_P2 — no OVER/UNDER yet (total games stays unpublished). Price ≠ Edge. A positive gap is not a bet recommendation. Always check available before reading bets[].
Sportsbooks
Odds keys: pinnacle, fanduel, draftkings, betmgm, caesars, bet365, circa, hardrock, betonline. Availability varies by event — check each bookmaker entry. Tennis intelligence fair reference is Pinnacle (single sharp book).
Examples
Today's WTA slate with odds inlined:
curl "https://lumify.ai/v1/events?sport=tennis&league=wta&status=scheduled&include_odds=true&limit=5" \
-H "Authorization: Bearer YOUR_API_KEY"
from lumify import Lumify client = Lumify(api_key="YOUR_API_KEY") page = client.events.list( sport="tennis", league="wta", status="scheduled", include_odds=True, limit=5, ) for event in page["events"]: print(event["id"], event["name"], event.get("odds"))
import { Lumify } from "@lumifyai/sdk"; const client = new Lumify({ apiKey: "YOUR_API_KEY" }); const { events } = await client.events.list({ sport: "tennis", league: "wta", status: "scheduled", includeOdds: true, limit: 5, }); events.forEach(e => console.log(e.id, e.name, e.odds));
Intelligence Price surface (1 credit when available):
curl "https://lumify.ai/v1/events/17438/intelligence" \
-H "Authorization: Bearer YOUR_API_KEY"
intel = client.events.intelligence(17438) if intel.get("available"): for bet in intel.get("bets") or []: print( bet.get("bet_type"), bet.get("probability"), bet.get("fair"), bet.get("best"), )
const intel = await client.events.intelligence(17438); if (intel.available) { for (const bet of intel.bets ?? []) { console.log(bet.bet_type, bet.probability, bet.fair, bet.best); } }
Player-keyed Data-layer stats — rankings, form, career serve/return rates:
curl "https://lumify.ai/v1/events/17438/stats" \
-H "Authorization: Bearer YOUR_API_KEY"
stats = client.events.stats(17438) if stats.get("available"): p1 = stats["players"]["player_1"] print(p1["name"], p1["ranking"], p1.get("career_rates"))
const stats = await client.events.stats(17438); if (stats.available) { const p1 = stats.players.player_1; console.log(p1.name, p1.ranking, p1.career_rates); }
Sample odds shape (abridged moneyline + games handicap):
{
"event_id": 17438,
"available": true,
"bookmakers": [
{
"bookmaker": "pinnacle",
"markets": [
{
"key": "spreads",
"label": "spread",
"outcomes": [
{ "outcome": "Alexandra Eala", "price": -115, "point": -3.5 },
{ "outcome": "Opponent", "price": -105, "point": 3.5 }
]
}
],
"captured_at": "2026-08-10T17:05:00Z"
}
]
}
Freshness
| Feed | Cadence | Fit |
|---|---|---|
| Live scores | ~1 minute (+ SSE/webhooks) | Scoreboards, in-match agents |
| Odds ingest | ~10 minutes (2-min response cache) | Research, line shop, alerts — not HFT |
| Stats | Rankings/form on ingest; career rates after vendor persists | Data layer, singles main-draw only |
| Intelligence | After publish runs | Check available; moneyline + spreads only |
Pricing
| Call | Credits |
|---|---|
| Most successful GETs (events, score, stats, intelligence, …) | 1 |
| Multi-book odds (bookmaker=all or a list) | 1 |
| available: false / errors | 0 |
Free Tier: 1,000 credits that never expire. Instant trial: 100 credits / 14 days, no signup. Details: /pricing.
For agents: machine-readable twin at /sports/tennis-api.md. MCP tools: list_events, get_odds, get_stats, get_intelligence, get_period_odds, list_ev — setup at /sports-mcp-server.
FAQ
Filter with sport=tennis (league atp or wta) for schedules, set-by-set live scores, moneyline / games-handicap / total-games odds across nine books, tennis-native /stats (rankings, form, career serve/return rates), and predictive /intelligence with a sharp fair + cross-book Price surface. Same event IDs across every layer.
Tennis /intelligence is Stage 1 — moneyline (ML_P1/ML_P2) and games handicap (SPREAD_P1/SPREAD_P2) only. Total games is not yet published on bets[], and GET /v1/intelligence/ev?market=totals returns 400 for tennis. Moneyline and spreads both clear on list_ev / bets[].ev (Beta) today.
No. Player props are NFL/NCAAF/NBA/NCAAB/NHL/MLB only, and public ticket%/handle% splits cover MLB/NBA/NHL/NFL only — both 400 for tennis. Team totals don't apply either (individual sport). Use odds, /stats, and intelligence (where available) on the same event IDs.
Beyond the schedule/score graph, GET /v1/events/{id}/stats returns each player's ATP/WTA singles ranking, rest days, trailing form (overall and filtered to this match's surface), a lookback win/loss record, and — when the vendor has persisted match-stats for that player — career serve/return rates (hold_pct, break_pts_converted, first/second-serve-won) and trailing surface win/loss. Plus up to 10 prior head-to-head meetings with set scores. No scoring or narrative — that's the Data layer.
No. /stats returns available: false unless draw_type is exactly singles and both player IDs resolve. Schedules, scores, and odds still cover the full ATP/WTA draw — the Data-layer aggregates are singles-only for now.
Start with the Tennis API
Create a free key — or use an instant trial key with no signup — and pull today's ATP/WTA slate with odds in under a minute.