Live Tennis API Broadcast API

The Broadcast API from Live Tennis API — 2 operation(s) for broadcast.

Business capability
Sports, Betting & Fantasy AE-180.80

Operations 2

GET /broadcast/match/{matchId} One flat object for on-air graphics (ULTRA) #
GET /broadcast/live The broadcast object for every live match (ULTRA) #

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-broadcast-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-broadcast-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Live Tennis Broadcast 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: Broadcast
paths:
  /broadcast/match/{matchId}:
    get:
      summary: One flat object for on-air graphics (ULTRA)
      description: 'Added 2026-09-24, from a broadcaster''s request. ONE flat JSON object for one match, built so a graphics template can bind each field to a fixed path.


        Three properties make it different from `GET /matches/{matchId}`, and each exists because a graphics pipeline breaks without it. The object is FLAT — no nested score object, no arrays of arrays. Every key is PRESENT on every read: a value we do not hold is `null`, never missing, because a missing key breaks a template binding. And the score is already in DISPLAY form — `p1_points` reads `"40"` or `"AD"`, `set_line` is one string such as `"5-7 2-0"`, and each set has its own column — so nothing has to be formatted downstream.


        It is a PROJECTION of what the public API already publishes: the match object, its embedded score and the model probability, plus the break-, set- and match-point flags derived by the same classifiers the scoring pipeline uses. Nothing here is a new fact and nothing is guessed.


        BUILT FOR 1 Hz. Every response carries a strong `ETag` and `Cache-Control: no-cache, max-age=1`; send `If-None-Match` and an unchanged state costs a 304 with no body. ULTRA''s burst window is 600 requests a minute, so ten single-match pollers at 1 Hz fit on one key. Detect a new state on `sequence`, not on `served_at`, which changes on every read.


        The key may be sent as `Authorization: Bearer `, as `X-API-Key`, or as `?token=` for tools that cannot set a header.'
      operationId: getBroadcastMatch
      parameters:
      - $ref: '#/components/parameters/matchId'
      responses:
        '200':
          description: The broadcast object for this match
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BroadcastObject'
        '304':
          description: Unchanged since the `ETag` you sent
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/UpgradeRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Broadcast
  /broadcast/live:
    get:
      summary: The broadcast object for every live match (ULTRA)
      description: 'The same object as `GET /broadcast/match/{matchId}`, for every actively-live match, in one call. A ticker or a multi-match wall polls this instead of one URL per match, and it stays ONE request however many matches are live. At most 200 objects.


        Same 1 Hz contract, same `ETag` and `Cache-Control`, same display-form fields, same every-key-present rule. An empty `data` means nothing is live right now and is the ordinary overnight answer, not a fault.'
      operationId: getBroadcastLive
      parameters:
      - name: tour
        in: query
        description: Restrict to one tour, same vocabulary as `?tour=` elsewhere.
        schema:
          type: string
      responses:
        '200':
          description: Every live match as a broadcast object
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/BroadcastObject'
                  count:
                    type: integer
                  served_at:
                    type: string
                    format: date-time
        '304':
          description: Unchanged since the `ETag` you sent
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Broadcast
components:
  parameters:
    matchId:
      name: matchId
      description: 'The match id. Every match route shares ONE id space, so the same value works everywhere `matchId` appears.


        Get it from any match list, where it is the `id` field of each match object:

        `GET /matches?status=live` (in progress), `GET /matches?status=upcoming` or `GET /fixtures` (scheduled), `GET /matches?status=completed` or `GET /history/matches` (finished).


        Ids are stable for the life of a match, so one captured before it starts still resolves after it finishes.

        '
      in: path
      required: true
      schema:
        type: integer
  responses:
    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'
    UpgradeRequired:
      description: Your tier doesn't unlock this endpoint
      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'
  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).
    BroadcastObject:
      type: object
      description: One flat object for a live-graphics template. EVERY key below is present on every read; a value we do not hold is `null` rather than absent, because a missing key breaks a template binding. Points and the set line are already in display form.
      properties:
        match_id:
          type: integer
        status:
          type: string
          description: '`live`, `completed`, `upcoming`'
        event_status:
          type:
          - string
          - 'null'
          description: The terminal badge where one is stated (`Finished`, `Retired`, `Walk Over`, `Interrupted`); null on a normal live row.
        outcome:
          type:
          - string
          - 'null'
        winner:
          type:
          - integer
          - 'null'
          description: 1 or 2, null until a result is established.
        tour:
          type:
          - string
          - 'null'
        tournament:
          type:
          - string
          - 'null'
        round:
          type:
          - string
          - 'null'
        surface:
          type:
          - string
          - 'null'
        best_of:
          type:
          - integer
          - 'null'
        is_doubles:
          type: boolean
        p1_id:
          type:
          - integer
          - 'null'
        p1_name:
          type:
          - string
          - 'null'
        p1_short:
          type:
          - string
          - 'null'
          description: Display short form, `"J. Sinner"`. A doubles team keeps each side's last token.
        p1_country:
          type:
          - string
          - 'null'
          description: Three-letter code.
        p1_ranking:
          type:
          - integer
          - 'null'
        p2_id:
          type:
          - integer
          - 'null'
        p2_name:
          type:
          - string
          - 'null'
        p2_short:
          type:
          - string
          - 'null'
        p2_country:
          type:
          - string
          - 'null'
        p2_ranking:
          type:
          - integer
          - 'null'
        p1_sets:
          type:
          - integer
          - 'null'
        p2_sets:
          type:
          - integer
          - 'null'
        p1_games:
          type:
          - integer
          - 'null'
          description: Games in the set in play.
        p2_games:
          type:
          - integer
          - 'null'
        p1_points:
          type:
          - string
          - 'null'
          description: 'As printed: `"0"`, `"15"`, `"30"`, `"40"`, `"AD"`, or the tiebreak count.'
        p2_points:
          type:
          - string
          - 'null'
        p1_set1:
          type:
          - integer
          - 'null'
          description: Per-set columns. A set not yet played is null on both sides.
        p2_set1:
          type:
          - integer
          - 'null'
        p1_set2:
          type:
          - integer
          - 'null'
        p2_set2:
          type:
          - integer
          - 'null'
        p1_set3:
          type:
          - integer
          - 'null'
        p2_set3:
          type:
          - integer
          - 'null'
        p1_set4:
          type:
          - integer
          - 'null'
        p2_set4:
          type:
          - integer
          - 'null'
        p1_set5:
          type:
          - integer
          - 'null'
        p2_set5:
          type:
          - integer
          - 'null'
        set_line:
          type:
          - string
          - 'null'
          description: The completed and in-play sets as one string, `"5-7 2-0"`.
        current_set:
          type:
          - integer
          - 'null'
        is_tiebreak:
          type: boolean
        match_tiebreak:
          type: boolean
          description: True while a deciding match tiebreak is in play.
        server:
          type:
          - integer
          - 'null'
          description: 1 or 2.
        p1_serving:
          type:
          - boolean
          - 'null'
        p2_serving:
          type:
          - boolean
          - 'null'
        break_point_for:
          type:
          - integer
          - 'null'
          description: 1, 2 or null. Cue an animation on the transition to non-null.
        set_point_for:
          type:
          - integer
          - 'null'
        match_point_for:
          type:
          - integer
          - 'null'
        p1_win_pct:
          type:
          - integer
          - 'null'
          description: The model probability as a whole percent. Null where no model figure was computed for this state — never guessed.
        p2_win_pct:
          type:
          - integer
          - 'null'
        sequence:
          type:
          - integer
          - 'null'
          description: The per-match accept counter. THIS is how a poller detects a new state.
        updated_at:
          type:
          - string
          - 'null'
          format: date-time
        age_seconds:
          type:
          - integer
          - 'null'
          description: Seconds since the score CONTENT last changed.
        stale:
          type:
          - boolean
          - 'null'
          description: A live match whose content has frozen past the threshold.
        served_at:
          type: string
          format: date-time
          description: When this response was built. Changes on every read — do not use it to detect a new state.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'Direct API key: Authorization: Bearer <key>'
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key