Live Tennis API Rankings API
The Rankings API from Live Tennis API — 1 operation(s) for rankings.
The Rankings API from Live Tennis API — 1 operation(s) for rankings.
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-rankings-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 Rankings 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: Rankings
paths:
/rankings:
get:
summary: Rankings and Elo — rank-ordered listing (PRO) or per-player as-of records…
description: 'Returns, per ranking system, the newest record effective ON OR BEFORE `as_of` — never one dated after it. Every other ranking field in this API is the player''s CURRENT value joined at read time, so replaying an old match elsewhere shows today''s ranks; this endpoint is the point-in-time answer. Systems are never collapsed into a single "rank": ATP/WTA and the ITF circuits carry rank+points, UTR carries a rating with null rank and points because it has neither. `meta.coverage.oldest_available` gives the earliest date each system can answer for — ITF and UTR observations reach back to 2026-06-01 (append-only per-player history accumulates from 2026-07-29) and nothing earlier can be reconstructed. TWO MODES — with `player` ids (ULTRA) — the per-player point-in-time records described above. WITHOUT `player` (PRO) — the FULL published table in rank order for exactly one `system`, the newest week at or before `as_of`; rows carry `player_name` as published and a null `player_id` for players outside our roster, so the table has no silent holes. `utr` has no listing (a rating, not a ranking). SYSTEM=UTR — observed ratings, honestly bounded. UTR records are observed from UTR''s public search: a rating UTR withholds appears as ABSENT, never as 0, and `rating` is the only populated value — `rank` and `points` are always null. Per-player as-of ONLY — there is deliberately no UTR listing, because a table of only the players we happen to track would be a fake leaderboard. Per-player history accumulates from 2026-07-29; scattered earlier single-snapshot observations reach back to 2026-06-01. Coverage is a deliberate bias, not a roster mirror: the 24-hour sweep targets players with no official rank and no Elo rating (so it skews ITF), and among players active in the last 60 days it holds 931 of 5,606 ITF players (16.6%), 197 of 1,903 Challenger (10.4%), 43 of 573 WTA (7.5%) and 15 of 525 ATP (2.9%) — measured 2026-08-17. SYSTEM=ELO — THE AS-OF ELO TAPE (ULTRA in BOTH modes). Our own computed Elo for 65,622 players on four independent ladders (overall, hard, clay, grass), back to 1877 (ATP) and 1968 (WTA), covering the main tours plus challengers plus the futures tier. It answers what a player was rated BEFORE a given match, which is the only shape a backtest can consume. It is a HISTORICAL TAPE rather than a live leaderboard — the corpus behind it is fixed and no longer receives new results, so `meta.coverage.newest_available` states the tape''s head date on every response (2026-06-15 at publication), and you should read it before treating the table as current. A week''s results become effective 14 days after that week begins — strictly after the longest event in tennis — so the failure direction is staleness, never look-ahead. `rating` is the Elo. `rank` is LISTING MODE ONLY and is null in per-player mode, because an Elo has no global rank at a past instant until you say which field and which activity window you mean. `points` is always null. `matches` is the count on THAT ladder, published so that a rating still near its 1500 cold start is visible rather than inferred. A ladder a player has never played is omitted, never substituted. Ratings are on our own scale, are not comparable with Elo published elsewhere, and do not decay — a surface played only a few weeks a year (grass in particular) moves slowly, so a rising player can sit below an established one for several seasons while beating them. A CURRENT per-player Elo remains free on `GET /players/{id}`; this is the point-in-time series, the leaderboard and the bulk export. That free rating is computed differently and sits on a DIFFERENT scale — the two differ by roughly 150 Elo of per-player standard deviation — so never present a rating from one scale against a rating from the other. The Elo listing REQUIRES `tour` — the ATP and WTA walks are disjoint, so a combined leaderboard would not be comparable — and takes exactly one `surface` (default `overall`).'
operationId: listRankings
parameters:
- name: player
in: query
required: false
schema:
type: array
items:
type: integer
maxItems: 50
explode: true
description: Player id — repeatable, max 50 (ULTRA per-player mode). OMIT it for the rank-ordered listing mode (PRO), which then requires exactly one `system`.
- name: archive_player
in: query
required: false
schema:
type: array
items:
type: integer
maxItems: 50
explode: true
description: Historical-corpus person id — repeatable, max 50, `system=elo` only, and REQUIRES `tour` because that id is only unique within one tour. It is the same id `GET /history/archive/players` returns as its `id`. Use it to reach the ~62,000 rated people who have no row in our player roster; `player` reaches the rest.
- name: tour
in: query
required: false
schema:
type: string
enum:
- atp
- wta
description: 'REQUIRED for an `elo` listing and for `archive_player`. The ATP and WTA Elo walks are disjoint universes whose players never meet, so a combined leaderboard would not be comparable. On an OFFICIAL system it is accepted when it agrees with that system''s own tour (`system=atp&tour=atp`) and REFUSED with 400 when it contradicts it. CORRECTION 2026-09-20: until that date `system=atp&tour=wta` returned 200 and the ATP table — the parameter was accepted and ignored. If you sent a contradicting pair, the rows you received were the table named by `system`.'
- name: surface
in: query
required: false
schema:
type: array
items:
type: string
enum:
- overall
- hard
- clay
- grass
explode: true
description: '`system=elo` only. Listing mode takes exactly one (default `overall`); per-player mode is repeatable and defaults to all four ladders. A ladder the player has never played is omitted, never substituted. REFUSED with 400 when no requested system is `elo` — there is no clay ATP ranking, so the filter cannot be made to act. CORRECTION 2026-09-20: until that date `system=atp&surface=clay` returned 200 and the OVERALL ATP table. A mixed `system=atp&system=elo&surface=clay` still passes, because an elo system is present.'
- name: min_matches
in: query
required: false
schema:
type: integer
minimum: 0
default: 20
description: '`system=elo` listing only. Minimum matches on that ladder. Every ladder starts at 1500, so without a floor the top of the table is players who won a handful of matches from the starting rating. Echoed back in `meta.coverage.qualified`. REFUSED with 400 when no requested system is `elo` (2026-09-20); it was previously accepted and ignored.'
- name: activity_weeks
in: query
required: false
schema:
type: integer
minimum: 1
maximum: 104
default: 52
description: '`system=elo` listing only. The ladder must have moved within this many weeks of `as_of`. Elo does not decay, so without an activity window every leaderboard is topped permanently by players who have stopped playing. Echoed back in `meta.coverage.qualified`. REFUSED with 400 when no requested system is `elo` (2026-09-20); it was previously accepted and ignored.'
- name: as_of
in: query
required: false
schema:
type: string
format: date
description: YYYY-MM-DD. Omit for the latest known record.
- name: system
in: query
required: false
schema:
type: array
items:
type: string
enum:
- atp
- wta
- itf_jt
- itf_mt
- itf_wt
- utr
- elo
- atp_doubles
- wta_doubles
explode: true
description: Restrict to one or more systems. Omit for all of the official systems — `elo`, `atp_doubles` and `wta_doubles` are NEVER included implicitly and must be named (a doubles tie prints as repeated ranks, as the publisher prints it), so an existing request's response is unchanged. Naming a system your plan does not cover refuses the whole call with 403 rather than silently returning the part you are entitled to.
- $ref: '#/components/parameters/limit'
- $ref: '#/components/parameters/offset'
responses:
'200':
description: Ranking records in force at `as_of`
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/RankingRecord'
meta:
$ref: '#/components/schemas/RankingListMeta'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/UpgradeRequired'
'429':
$ref: '#/components/responses/RateLimited'
tags:
- Rankings
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
responses:
BadRequest:
description: Bad query parameter
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
UpgradeRequired:
description: Your tier doesn't unlock this endpoint
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:
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).
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.
RankingListMeta:
allOf:
- $ref: '#/components/schemas/ListMeta'
- type: object
properties:
coverage:
type: object
description: What resolved against what was asked. Read it before trusting an empty result — ITF and UTR observations reach back to 2026-06-01 (append-only per-player history accumulates from 2026-07-29); nothing earlier can be reconstructed, so a request before `oldest_available` correctly returns nothing for those systems.
properties:
as_of:
type:
- string
- 'null'
format: date
effective_date:
type:
- string
- 'null'
format: date
description: Listing mode. For the official systems, the published week the table is from. For `elo`, the as-of instant the leaderboard was resolved at (each row carries its own `effective_date`, since a sparse tape moves per player).
players_requested:
type: integer
description: Per-player mode only.
players_resolved:
type: integer
description: Per-player mode only.
systems_requested:
type: array
items:
type: string
systems_resolved:
type: array
items:
type: string
oldest_available:
type: object
additionalProperties:
type:
- string
- 'null'
format: date
description: Earliest effective date held, per requested system.
newest_available:
type: object
additionalProperties:
type:
- string
- 'null'
format: date
description: Present only when `elo` was requested. The NEWEST effective date the Elo tape holds. The corpus behind it is fixed and no longer receives new results, so this date does not advance and will keep receding from today — that is a property of a historical tape, not a fault. Read it before treating an Elo leaderboard as a current one.
tour:
type:
- string
- 'null'
description: Present only when `elo` was requested — which walk answered.
surface:
description: Present only when `elo` was requested. The ladder (listing) or the list of ladders (per-player) the response covers.
players_rated:
type: integer
description: Present only when `elo` was requested. People with at least one rating in the tape.
players_linked:
type: integer
description: Present only when `elo` was requested. How many of those have a row in our player roster, and therefore can be reached with `player`. The rest are reachable with `archive_player` and appear on the leaderboard with a null `player_id` and their name. The gap between these two numbers is large and is reported rather than buried.
qualified:
type: object
description: Present only on an `elo` listing. The two filters that DEFINE the field this table ranks. A top-N is not quotable without them — change either and it is a different top-N, and neither is more correct than the other.
properties:
activity_weeks:
type: integer
min_matches:
type: integer
model:
type: object
description: Present only when `elo` was requested. What the numbers ARE.
properties:
name:
type: string
start_rating:
type: number
k_base:
type: number
publication_lag_days:
type: integer
description: A result becomes effective this many days after its tournament WEEK begins. The corpus dates a match by its tournament start, so without the lag a Slam final would be visible under the Monday the Slam began. The lag clears the longest event in tennis, so the failure direction is staleness and never look-ahead.
built_at:
type:
- string
- 'null'
format: date-time
note:
type: string
RankingRecord:
type: object
description: One ranking record in force at the requested instant. `system` is always explicit and the systems are never collapsed into a single "rank" — they are not comparable. ATP/WTA and the ITF circuits populate rank+points; UTR populates rating and leaves rank/points null, because UTR is a rating and has no rank.
properties:
player_id:
type:
- integer
- 'null'
description: Null only on listing rows for players outside our roster — the listing is kept independent of the roster precisely so a top-N has no silent holes. Always present on per-player records for the official systems and UTR. For `elo` it is also null on rows for the ~62,000 rated people with no roster row — normal, not an error; `player_name` is always populated, and `archive_player` fetches that person directly.
player_name:
type:
- string
- 'null'
description: The name as the ranking publisher printed it — present on listing rows (where `player_id` may be null), absent on per-player records.
system:
type: string
enum:
- atp
- wta
- itf_jt
- itf_mt
- itf_wt
- utr
- elo
- atp_doubles
- wta_doubles
tour:
type:
- string
- 'null'
rank:
type:
- integer
- 'null'
description: Null for UTR. Null for `elo` in per-player mode too — an Elo has no global rank at a past instant until you say which field and which activity window you mean, and the per-player read is given neither. In `elo` listing mode it is the position in the qualified field of THAT call.
points:
type:
- integer
- 'null'
description: Null for UTR and for `elo` — both are ratings, and neither has points.
surface:
type: string
enum:
- overall
- hard
- clay
- grass
description: '`elo` only, and absent on every other system. Which of the four independent ladders this rating is. A ladder the player has never played is omitted from the response rather than substituted with another.'
matches:
type:
- integer
- 'null'
description: '`elo` only, and absent on every other system. Cumulative matches on THAT ladder at that date — not the player''s career total. Every ladder starts at 1500, so this is what tells you whether a rating is an assessment or a cold start, and it is the number `min_matches` filters on.'
previous_rank:
type:
- integer
- 'null'
description: The rank at the immediately preceding snapshot week (ATP/WTA only; null when no prior week is held, and always null for ITF/UTR).
rank_movement:
type:
- integer
- 'null'
description: The circuit's own signed weekly movement (ITF systems only; null elsewhere).
rating:
type:
- number
- 'null'
description: UTR and `elo` only; null elsewhere. For `elo` this is the Elo itself, on our own scale, which is not comparable with Elo published anywhere else.
effective_date:
type:
- string
- 'null'
format: date
description: The publication week this record took effect. For records ingested live rather than from the official weekly publication this is bucketed to the observed week, so it can sit up to six days later than the moment the value actually took effect; where both exist for one week the official publication is served.
observed_at:
type:
- string
- 'null'
format: date-time
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Direct API key: Authorization: Bearer <key>'
apiKeyHeader:
type: apiKey
in: header
name: X-API-Key