Live Tennis API · docs · version 1.13.4

Tennis players, tournaments and rankings API

How do you look up a player, a tournament or a ranking? GET /players searches by name and GET /players/{playerId} returns one player's bio, current ranking and cached statistics — both FREE. GET /tournaments is the catalogue that Match.tournament_id joins against, so a match can be resolved to its event without a second vendor. GET /rankings (PRO) serves rank-ordered listings and per-player as-of records, including Elo.

5 endpoints on this page. Base URL https://api.livetennisapi.com/api/public/v1; authenticate with the X-API-Key header. Plans involved: FREE, PRO. A free key needs no card.

GET /players

Search players by name (FREE)

Plan required: FREE · operationId: searchPlayers

Parameters

GET /players — parameters
NameInTypeRequiredNotes
searchquerystringno
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /players — responses
StatusMeaning
200Players (ranked first; no stats object on the list)
401Missing, unknown, or disabled credentials
429Rate 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.

Response fields

GET /players — response fields
FieldTypeDescription
dataarray of object
metaobject

Example

curl https://api.livetennisapi.com/api/public/v1/players \
  -H "Authorization: Bearer twjp_..."

GET /players/{playerId}

One player's bio + ranking + cached stats (FREE)

Plan required: FREE · operationId: getPlayer

Parameters

GET /players/{playerId} — parameters
NameInTypeRequiredNotes
playerIdpathintegeryes

Responses

GET /players/{playerId} — responses
StatusMeaning
200Player with stats ({ratings, season})
401Missing, unknown, or disabled credentials
404No such resource, or no data yet
429Rate 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.

Response fields

GET /players/{playerId} — response fields
FieldTypeDescription
idinteger
namestring
tourstring or nullThe record's OWN tour, which is NOT the tour filter vocabulary. It is granular (juniors_boys, juniors_girls, challenger_men) where the filter is grouped (juniors, challenger), and a doubles team reports it UPPERCASE (ATP) where an individual reports lowercase (atp). Treat it as an opaque string; do not parse it into the filter enum.
countrystring or null
rankinginteger or nullOfficial singles ranking POSITION (the ordinal: 1, 25, 100) — never points, seed, race, UTR, Elo or a doubles rank. Men from the official ATP table, women from the WTA table, chosen by the player (a Challenger or ITF man who holds an ATP ranking carries it here). Refreshed from the standings ahead of each match the player has with us; a player who has not played recently keeps their last refreshed position. null when no ranking is held (doubles teams, most ITF/junior-only players). Always the CURRENT record, even on historical matches — use /rankings?as_of= for the rank in force on a date.
ranking_pointsinteger or nullPoints on the same official table, refreshed with ranking.
ranking_movementstring or null (up, down, same, null)
handstring or null (R, L, null)
backhandinteger or null (1, 2, null)
birthdaystring or null
is_doubles_teamboolean
data_completenessobjectHow much biographical detail is known for this player, so a consumer can distinguish "not in the feed" from "not yet fetched" without probing. Present on every player in a match payload. Lower tours carry far less of it than main tour.
statsobjectSingle-player endpoint only

Example

curl https://api.livetennisapi.com/api/public/v1/players/1104 \
  -H "Authorization: Bearer twjp_..."

GET /tournaments

Tournament catalogue — the id space Match.tournament_id joins (FREE)

Plan required: FREE · operationId: listTournaments

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.

Parameters

GET /tournaments — parameters
NameInTypeRequiredNotes
searchquerystringnoCase-insensitive substring match on the tournament name.
tourquerystring (atp, wta, challenger, itf, juniors)noRestrict 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.
drawquerystring (singles, doubles)noDraw 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.
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /tournaments — responses
StatusMeaning
200Tournaments, name order
400Bad query parameter
401Missing, unknown, or disabled credentials
429Rate 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.

Response fields

GET /tournaments — response fields
FieldTypeDescription
dataarray of object
metaobject

Example

curl https://api.livetennisapi.com/api/public/v1/tournaments \
  -H "Authorization: Bearer twjp_..."

GET /tournaments/{tournamentId}

One tournament by its stable id (FREE)

Plan required: FREE · operationId: getTournament

Parameters

GET /tournaments/{tournamentId} — parameters
NameInTypeRequiredNotes
tournamentIdpathstringyesThe tournament_id carried on match objects.

Responses

GET /tournaments/{tournamentId} — responses
StatusMeaning
200The tournament
401Missing, unknown, or disabled credentials
404No such resource, or no data yet
429Rate 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.

Response fields

GET /tournaments/{tournamentId} — response fields
FieldTypeDescription
idstringThe stable id Match.tournament_id joins.
namestring or null
tourstring or null (atp, wta, challenger, itf, juniors, null)
surfacestring or null (hard, clay, grass, null)
indoorboolean
citystring or nullHost city, from a curated table — null where not curated.
countrystring or nullHost 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.)
categorystring or null (grand_slam, masters_1000, tour_finals, atp_500, atp_250, wta_1000, wta_500, wta_250, wta_125, challenger, itf, juniors, null)Tournament category where our catalogues agree unambiguously on an exact-name join — null otherwise, never derived from the name.

Example

curl https://api.livetennisapi.com/api/public/v1/tournaments/{tournamentId} \
  -H "Authorization: Bearer twjp_..."

GET /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

Plan required: PRO · operationId: listRankings

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.

Parameters

GET /rankings — parameters
NameInTypeRequiredNotes
playerqueryarray of integernoPlayer id — repeatable, max 50 (ULTRA per-player mode). OMIT it for the rank-ordered listing mode (PRO), which then requires exactly one system.
archive_playerqueryarray of integernoHistorical-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.
tourquerystring (atp, wta)noREQUIRED 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.
surfacequeryarray of string (overall, hard, clay, grass)nosystem=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.
min_matchesqueryintegernosystem=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. Default 20.
activity_weeksqueryintegernosystem=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. Default 52.
as_ofquerystringnoYYYY-MM-DD. Omit for the latest known record.
systemqueryarray of string (atp, wta, itf_jt, itf_mt, itf_wt, utr, elo, atp_doubles, wta_doubles)noRestrict 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.
limitqueryintegerno Default 50.
offsetqueryintegerno Default 0.

Responses

GET /rankings — responses
StatusMeaning
200Ranking records in force at as_of
400Bad query parameter
401Missing, unknown, or disabled credentials
403Your tier doesn't unlock this endpoint
429Rate 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.

Response fields

GET /rankings — response fields
FieldTypeDescription
dataarray of object
metaobject

Example

curl https://api.livetennisapi.com/api/public/v1/rankings \
  -H "Authorization: Bearer twjp_..."