Live Tennis API Tournaments API
The Tournaments API from Live Tennis API — 2 operation(s) for tournaments.
The Tournaments API from Live Tennis API — 2 operation(s) for tournaments.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/livetennisapi-com:livetennisapi-com-tournaments-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Live Tennis Tournaments API
version: 1.13.76
contact:
name: Live Tennis API
url: https://livetennisapi.com
license:
name: MIT
identifier: MIT
termsOfService: https://livetennisapi.com/terms
description: 'Real-time tennis scores, player data, match-winner market prices, and
model-driven match analysis. Read-only. Coverage spans ATP, WTA,
Challenger, ITF and the junior Grand Slam draws — depth differs by tour
and surface; `GET /history/coverage` states the measured numbers. Access is tiered (FREE / BASIC / PRO / ULTRA). Each tier includes
everything in the tiers below it; the concrete deltas are:
`FREE` — self-serve, no card (https://livetennisapi.com/subscribe/free). Live and upcoming matches, current scores, players, fixtures, the
tournament catalogue (`/tournaments`), and your own usage stats. 30 requests/minute, 100/day. No market prices, no model fields, no
WebSocket. Historical results are not part of the tier, but a FREE key
may spend 20 calls per calendar month on the history endpoints as a
taste of the product — served exactly like an entitled call. Past that
they answer `403 upgrade_required` carrying
`free_history_taste: "used"`. `BASIC` — adds historical data: the completed-match listing
(`/history/matches`, and `status=completed` on `/matches`), the
per-match point-by-point tape with the model win-probability on the
rows where the model ran
(`/history/matches/{matchId}`), the measured completeness rollup
(`/history/coverage`), and the results archive (1968–2022) —
deep results (`/history/archive/matches`), archive player bios
(`/history/archive/players`), career aggregates
(`/history/archive/career`) and head-to-head (`/h2h`). 60 requests/minute, 1,000/day. `PRO` — adds match events (`/matches/{matchId}/events`), market prices
(`/markets`, `/markets/{matchId}/prices`, `/matches/{matchId}/prices`),
the pre-built monthly bulk history packages (`/history/packages`), the
rank-ordered rankings listing (`/rankings?system=`) and one player''s
win/loss and set-based record (`/players/{playerId}/stats`). 300 requests/minute, 10,000/day. `ULTRA` — adds model analysis (`/matches/{matchId}/analysis`), the live
model fields (`win_probability_p1`, `danger`) on every score object,
in-play match statistics (`/matches/{matchId}/statistics`) and their
per-player sums (`serve_return` on `/players/{playerId}/stats`), per-player
as-of ranking records (`/rankings?player=`), the as-of Elo tape
(`/rankings?system=elo` — both modes, plus `kind=elo` bulk packages),
rally construction
(`/rally/matches`, shot-by-shot charted data), career and per-match
charting stats (`/charting/players`, `/charting/matches/{chartingMatchId}`),
the reconstructed 2013–2022 archive tape
(`/history/archive/matches/{archiveId}/tape` — also opened by ANY active
History plan, Starter included), the WebSocket live feed at `/ws` and the
high-fan-out push feed (`/ws-token`), and outbound webhooks (direct keys). 600 requests/minute, 500,000/day. History runs in two continuous halves, deliberately non-overlapping: the
point-by-point tape (2023→now) covers January 2023 to now, match by
match, point by point; the results archive (1968–2022) covers 1968
through 2022 as winner/loser-shaped RESULTS (final score, seeds, ranks at
the time — no point-by-point). The archive ends exactly where the tape
begins, so no match is ever served from two datasets. Archive results played **2013–2022** additionally carry a RECONSTRUCTED
point-by-point tape at `/history/archive/matches/{archiveId}/tape` — the
score sequence behind the published result: reconstructed point sequences,
no capture timestamps. 97,901 matches / 14,340,663 rows, seasons **2013–2022
ONLY**: the archive holds a further 977,903 results from 1968–2012 and NOT
ONE of them has a tape, because we hold no point sequence for those years and
we do not manufacture one. Write the range as
2013–2022, never as "pre-2023" — the second phrasing reads as 1968 onward
and is wrong by 45 seasons.'
servers:
- url: https://api.livetennisapi.com/api/public/v1
security:
- bearerAuth: []
- apiKeyHeader: []
tags:
- name: Tournaments
paths:
/tournaments:
get:
summary: Tournament catalogue — the id space `Match.tournament_id` joins (FREE)
description: Stable tournament identity, one row per tournament × event type, stable across seasons. `city`/`country` come from a curated table and `category` only where our catalogues agree unambiguously on an exact-name join — each is null otherwise, never derived from the tournament name. Each row also carries the CURRENT season's `tier` / `tier_source` (added 2026-09-22) — the level in a closed vocabulary (`atp_500`, `challenger_125`, `wta_125`, `itf_w35` … | null); the tier is per season, so a match carries its own.
operationId: listTournaments
parameters:
- name: search
in: query
schema:
type: string
description: Case-insensitive substring match on the tournament name.
- $ref: '#/components/parameters/tour'
- $ref: '#/components/parameters/draw'
- name: tier
in: query
required: false
schema:
type: string
description: 'Tier filter (added 2026-09-24): comma-separated exact values from the same closed tier vocabulary as `Match.tier` (`?tier=atp_500`, `?tier=atp_500,challenger_100`). Selects tournaments whose own `tier` field carries one of the values, which on this resource is the **current season''s** level, because a catalogue row has no season of its own. That is the one difference from `?tier=` on `/matches` and `/history/matches`, where the value is the level in each MATCH''S season (a tournament keeps one `tournament_id` while its level moves). Filter and field always read the same season on the same resource, so they cannot disagree. A tournament with no row for the current season matches no value: null means the level is unknown, not absent. Composes with `?tour=`, `?draw=` and `?search=`. Unknown value → 400 `bad_tier` with the offending values in `bad` and the full vocabulary in `allowed`.'
- $ref: '#/components/parameters/limit'
- $ref: '#/components/parameters/offset'
responses:
'200':
description: Tournaments, name order
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Tournament'
meta:
$ref: '#/components/schemas/ListMeta'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
tags:
- Tournaments
/tournaments/{tournamentId}:
get:
summary: One tournament by its stable id (FREE)
operationId: getTournament
parameters:
- name: tournamentId
in: path
required: true
schema:
type: string
description: The `tournament_id` carried on match objects.
responses:
'200':
description: The tournament
content:
application/json:
schema:
$ref: '#/components/schemas/Tournament'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
tags:
- Tournaments
components:
parameters:
offset:
name: offset
in: query
schema:
type: integer
default: 0
minimum: 0
limit:
name: limit
in: query
schema:
type: integer
default: 50
minimum: 1
maximum: 200
tour:
name: tour
in: query
description: Restrict results to one tour. Each value covers its singles and doubles draws, so `atp` includes ATP doubles and `juniors` covers the boys' and girls' Grand Slam draws. Omit for all tours. An unrecognised value is a 400 rather than a silent pass-through, so a caller never receives a tour it did not ask for. Applied before pagination, so `meta.count` reflects the filtered set.
schema:
type: string
enum:
- atp
- wta
- challenger
- itf
- juniors
draw:
name: draw
in: query
schema:
type: string
enum:
- singles
- doubles
description: 'Draw filter (added 2026-08-18) — the axis the tour filter deliberately collapses; the two compose (`?tour=itf&draw=doubles` is the ITF doubles slice). Same vocabulary as the `draw` field on Match, decided by the same shared definition, so filter and field cannot disagree. A row whose draw is null — a team tie, or no stated event type and no doubles-team participant — matches NEITHER value: null is an answer, not a wildcard. Two honesty notes: on /tournaments the answer comes from the event type alone (a tournament row has no participants to supply the doubles-team evidence matches have), and draw=doubles alone also returns mixed and exhibition doubles that no tour value reaches. An unknown value is a 400 bad_draw with the allowed values.'
responses:
BadRequest:
description: Bad query parameter
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: No such resource, or no data yet
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: Missing, unknown, or disabled credentials
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RateLimited:
description: 'Rate limit exceeded (Retry-After header present). Three body shapes, told apart by `error` and `scope`: the per-MINUTE limit (`rate_limited`, with `upgrade_url`, `tier` and `price` naming the next tier up); the per-DAY quota (`rate_limited` with `scope: "day"`, `limit_per_day`, and `resets_at` — the absolute ISO instant the daily window resets); and `abuse_throttled` with `retry_at_epoch` — a 24-hour block applied to clients that keep hammering far past their cap, which a well-behaved retry loop never sees. Fix the loop rather than retrying through it.'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
ListMeta:
type: object
properties:
limit:
type: integer
offset:
type: integer
count:
type: integer
total:
type:
- integer
- 'null'
description: Size of the whole filtered set. Null when it cannot be counted cheaply.
has_more:
type: boolean
description: More results exist beyond this page. Read this rather than comparing count to limit. For a complete enumeration of a filtered set, page `offset` forward by `limit` until this is false; on `completed`/`cancelled` listings `total` is null and this is the only end-of-data signal.
next_cursor:
type:
- string
- 'null'
description: Change-feed only (`?updated_since=` / `?cursor=`). Opaque token to pass back as `?cursor=`. Null means you are caught up — stop paging and switch to polling from `watermark`.
watermark:
type:
- string
- 'null'
format: date-time
description: Change-feed only. The floor to use on your NEXT poll. Deliberately trails the server clock by 5s so a row committed during this request cannot fall through the gap.
Error:
type: object
properties:
error:
type: string
description: Stable machine-readable code, e.g. bad_date, bad_coverage, bad_sequence, bad_points, bad_points_complete, bad_combination, points_read_disabled, bad_after_seq, bad_changed_since, bad_restated_since, points_disabled, bad_period, bad_year, bad_format, bad_tour, bad_tier, bad_country, ambiguous_name, not_charted, not_found, no_analysis, no_market, upgrade_required, rate_limited, abuse_throttled.
detail:
type: string
description: Human-readable explanation, when one adds anything.
allowed:
type: array
items:
type: string
description: On a rejected enumerated parameter, the values that would have been accepted (e.g. the coverage vocabulary for bad_coverage).
Tournament:
type: object
description: One tournament of the catalogue — the stable id space `Match.tournament_id` joins, one row per tournament × event type, stable across seasons.
properties:
id:
type: string
description: The stable id `Match.tournament_id` joins.
name:
type:
- string
- 'null'
tour:
type:
- string
- 'null'
enum:
- atp
- wta
- challenger
- itf
- juniors
- null
surface:
type:
- string
- 'null'
enum:
- hard
- clay
- grass
- null
description: 'Court surface, null where the catalogue holds none — 61 of 10,287 tournaments on 2026-09-20. 35 of those are team competitions (Davis Cup, Billie Jean King Cup, Laver Cup), which have no tournament-level surface to hold: each tie is played on the host nation''s choice, and that surface is published on each MATCH of the tie instead. A surface value that is not one of hard/clay/grass is rejected rather than published. 3 are the exhibition catch-all and 23 are ordinary events with no stated surface.'
indoor:
type: boolean
city:
type:
- string
- 'null'
description: Host city, from a curated table — null where not curated.
country:
type:
- string
- 'null'
description: Host country, ISO-3166 alpha-2 — null where not curated. (NOTE this differs from `player.country` and the `?country=` filter, which use IOC-style lowercase 3-letter codes.)
category:
type:
- string
- 'null'
enum:
- grand_slam
- masters_1000
- tour_finals
- atp_500
- atp_250
- wta_1000
- wta_500
- wta_250
- wta_125
- challenger
- itf
- juniors
- null
description: Tournament category where our catalogues agree unambiguously on an exact-name join — null otherwise, never derived from the name. Unchanged by `tier` (2026-09-22) and remains the coarse class; read `tier` for the level.
tier:
type:
- string
- 'null'
enum:
- grand_slam
- atp_finals
- atp_1000
- atp_500
- atp_250
- next_gen_finals
- united_cup
- davis_cup
- olympics
- laver_cup
- wta_finals
- wta_elite_trophy
- wta_1000
- wta_500
- wta_250
- wta_125
- bjk_cup
- challenger_175
- challenger_125
- challenger_100
- challenger_75
- challenger_50
- itf_m15
- itf_m25
- itf_w15
- itf_w25
- itf_w35
- itf_w40
- itf_w50
- itf_w60
- itf_w75
- itf_w80
- itf_w100
- juniors
- utr
- exhibition
- null
description: 'The level this tournament is played at in the CURRENT season (added 2026-09-22) — the same closed vocabulary as `tier` on a match (`grand_slam`, `atp_1000` … `atp_250`, `wta_1000` … `wta_125`, `challenger_175` … `challenger_50`, `itf_m15` / `itf_m25`, `itf_w15` … `itf_w100`, the team events, `juniors`, `utr`, `exhibition`). A catalogue row has no season of its own; a match does: the tier is PER SEASON and it moves under one id — Dallas was `atp_250` in 2023 and 2024 and `atp_500` from 2025; Estoril `atp_250`, then `challenger_175` in 2025, then `atp_250` again in 2026; Eastbourne `wta_500` through 2024 and `wta_250` from 2025 — so read the tier off each MATCH for its season, never copy this value onto a match. Null when this season''s calendar does not list the event — never guessed.'
tier_source:
type:
- string
- 'null'
enum:
- calendar
- name
- wikipedia
- resolver
- null
description: 'How `tier` was established (added 2026-09-22): `calendar` = the official per-season tour calendar named the level (ATP tour and Challenger calendars, the WTA tournament list); `name` = an unambiguous name rule (the ITF category is in the event''s official name — `M25 …`, `W35 …` — team and UTR events are named as such, and the Slam junior draws and exhibitions are their own event type); `wikipedia` = a published season schedule, used only where the official calendar could not state the level for that season (Eastbourne 2023–2025); `resolver` = resolved after the seed dataset, by the same rules, from the event''s name, tour and dates in that season. Null exactly when `tier` is null.'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Direct API key: Authorization: Bearer <key>'
apiKeyHeader:
type: apiKey
in: header
name: X-API-Key