---
title: "Rankings and Elo — rank-ordered listing (PRO) or per-player as-of records (ULTRA); the as-of Elo tape is ULTRA in both modes"
method: GET
path: "/rankings"
---

# Rankings and Elo — rank-ordered listing (PRO) or per-player as-of records (ULTRA); the as-of Elo tape is ULTRA in both modes

`GET /rankings`

## Request

- Base URL: `https://api.livetennisapi.com/api/public/v1`
- URL: `https://api.livetennisapi.com/api/public/v1/rankings`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

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 sourced 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`). `elo` is never implicit — omitting `system` returns the official systems only.

## Query parameters

- `player` integer[]
- `archive_player` integer[]
- `tour` 'atp' | 'wta'
- `surface` string[]
- `min_matches` integer
- `activity_weeks` integer
- `as_of` string, date
- `system` string[]
- `limit` integer
- `offset` integer

## Response `200`

Ranking records in force at `as_of`

- object
  - `data` RankingRecord[]
    - `player_id` integer, nullable: 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` string, nullable: The name as the ranking publisher printed it — present on listing rows (where `player_id` may be null), absent on per-player records.
    - `system` 'atp' | 'wta' | 'itf_jt' | 'itf_mt' | 'itf_wt' | 'utr' | 'elo' | 'atp_doubles' | 'wta_doubles'
    - `tour` string, nullable
    - `rank` integer, nullable: 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` integer, nullable: Null for UTR and for `elo` — both are ratings, and neither has points.
    - `surface` 'overall' | 'hard' | 'clay' | 'grass': `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` integer, nullable: `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` integer, nullable: 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` integer, nullable: The circuit's own signed weekly movement (ITF systems only; null elsewhere).
    - `rating` number, nullable: 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` string, date, nullable: 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` string, date-time, nullable
  - `meta` RankingListMeta
    - `limit` integer
    - `offset` integer
    - `count` integer
    - `total` integer, nullable: Size of the whole filtered set. Null when it cannot be counted cheaply.
    - `has_more` boolean: 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` string, nullable: 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` string, date-time, nullable: 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.
    - `coverage` object: 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.
      - `as_of` string, date, nullable
      - `effective_date` string, date, nullable: 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` integer: Per-player mode only.
      - `players_resolved` integer: Per-player mode only.
      - `systems_requested` string[]
      - `systems_resolved` string[]
      - `oldest_available` object: Earliest effective date held, per requested system.
      - `newest_available` object: 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` string, nullable: Present only when `elo` was requested — which walk answered.
      - `surface` unknown
      - `players_rated` integer: Present only when `elo` was requested. People with at least one rating in the tape.
      - `players_linked` integer: 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` object: 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.
        - `activity_weeks` integer
        - `min_matches` integer
      - `model` object: Present only when `elo` was requested. What the numbers ARE.
        - `name` string
        - `start_rating` number
        - `k_base` number
        - `publication_lag_days` integer: 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` string, date-time, nullable
        - `note` string

## Other responses

- `400`: Bad query parameter
- `401`: Missing, unknown, or disabled credentials
- `403`: Your tier doesn't unlock this endpoint
- `429`: 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.

---

[API](https://skmtc.dev/livetennisapi/apis/live-tennis-api.md) · [All operations](https://skmtc.dev/livetennisapi/apis/live-tennis-api/llms.txt) · [OpenAPI document](https://skmtc.dev/livetennisapi/apis/live-tennis-api/revisions/7881947e8e43?raw)
