Live Tennis API Rankings API

The Rankings API from Live Tennis API — 1 operation(s) for rankings.

Business capability
Sports, Betting & Fantasy AE-180.80

Operations 1

GET /rankings Rankings and Elo — rank-ordered listing (PRO) or per-player as-of records… #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • 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.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/livetennisapi-com:livetennisapi-com-rankings-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

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 Specification

livetennisapi-com-rankings-api-openapi.yml Raw ↑
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