Live Tennis API Tournaments API

The Tournaments API from Live Tennis API — 2 operation(s) for tournaments.

Business capability
Sports, Betting & Fantasy AE-180.80

Operations 2

GET /tournaments Tournament catalogue — the id space `Match.tournament_id` joins (FREE) #
GET /tournaments/{tournamentId} One tournament by its stable id (FREE) #

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-tournaments-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-tournaments-api-openapi.yml Raw ↑
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