Live Tennis API Fixtures API

The Fixtures API from Live Tennis API — 1 operation(s) for fixtures.

Business capability
Sports, Betting & Fantasy AE-180.80

Operations 1

GET /fixtures Upcoming scheduled fixtures, earliest first (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-fixtures-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-fixtures-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Live Tennis Fixtures 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: Fixtures
paths:
  /fixtures:
    get:
      summary: Upcoming scheduled fixtures, earliest first (FREE)
      operationId: listFixtures
      parameters:
      - $ref: '#/components/parameters/tour'
      - $ref: '#/components/parameters/draw'
      - $ref: '#/components/parameters/isQualifying'
      - $ref: '#/components/parameters/gender'
      - $ref: '#/components/parameters/limit'
      - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Fixtures with start time and player ids where resolved — the nulls are real states, not gaps (names are always present)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Fixture'
                  meta:
                    $ref: '#/components/schemas/ListMeta'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Fixtures
components:
  parameters:
    isQualifying:
      name: is_qualifying
      in: query
      required: false
      schema:
        type: boolean
      description: 'Return only matches in the QUALIFYING draw (`true`) or only those the source states are not (`false`), matching the `is_qualifying` flag each row carries (field added 2026-08-12, filter added 2026-10-02). Omit for both. Anything other than true/false is a 400 `bad_is_qualifying`, never a silently ignored filter.

        THIS IS THE ONLY WAY TO SPLIT THE TWO DRAWS, and the reason is in the `is_qualifying` field description: the two are routinely carried under ONE tournament name with ONE round vocabulary, so `tournament`, `tournament_id`, `round` and `round_code` all read main-draw for a qualifying match. Do not filter on the round text.

        THREE-VALUED, so this filter is too: a row whose flag is `null` (no source has ever stated it) matches NEITHER value and is returned only when the filter is omitted. `null` is an answer, not a `false` — returning it under `is_qualifying=false` would assert "main draw" on the strength of nothing. Measured 2026-10-02 over completed matches: the flag is stated on 98.9% of the last 30 days and 98.3% of 2026 outside the UTR circuit, where it is mostly unstated (1,695 of the 1,831 unstated rows in the last 30 days are UTR).'
    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.'
    gender:
      name: gender
      in: query
      required: false
      schema:
        type: string
        enum:
        - men
        - women
      description: "Return only men's or only women's matches, matching the `gender` field each row carries (added 2026-09-09, documented here 2026-10-02). Omit for both. Anything other than men/women is a 400 `bad_gender`, never a silently ignored filter.\nIt exists because `?tour=` deliberately collapses: `challenger` is ATP Challenger AND WTA 125, and `itf` and `juniors` are both sexes — so without this there is no way to ask for men's Challenger at all. A row whose `gender` is `null` (mixed doubles, a team tie) matches NEITHER value, the same contract as `?draw=` and `?is_qualifying=`.\n    bad_points_complete, bad_combination, points_read_disabled, bad_after_seq, bad_has_market, no_market, points_disabled,"
  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.
    Fixture:
      type: object
      properties:
        id:
          type: integer
        event_date:
          type:
          - string
          - 'null'
          format: date
        start_time:
          type:
          - string
          - 'null'
          format: date-time
          description: Scheduled start (UTC). Null until the order of play assigns a time — a date-only fixture is a real state, and rows written before 2026-08-03 stay null.
        player1_id:
          type:
          - integer
          - 'null'
          description: Our player id, when the participant is in our roster (exact-key resolution, never a name match). Null otherwise — names are always present regardless.
        player2_id:
          type:
          - integer
          - 'null'
          description: As `player1_id`, for the second participant.
        tour:
          type:
          - string
          - 'null'
          description: The 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.
        tournament:
          type:
          - string
          - 'null'
        round:
          type:
          - string
          - 'null'
        round_code:
          type:
          - string
          - 'null'
          enum:
          - F
          - SF
          - QF
          - R16
          - R32
          - R64
          - R128
          - RR
          - BR
          - Q
          - Q1
          - Q2
          - Q3
          - Q4
          - ER
          - null
          description: Normalized round — same vocabulary as `Match.round_code`. Null when the label is unrecognised, never guessed.
        surface:
          type:
          - string
          - 'null'
        player1_name:
          type:
          - string
          - 'null'
        player2_name:
          type:
          - string
          - 'null'
        status:
          type:
          - string
          - 'null'
  responses:
    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'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'Direct API key: Authorization: Bearer <key>'
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key