Live Tennis API Rally API

The Rally API from Live Tennis API — 2 operation(s) for rally.

Business capability
Sports, Betting & Fantasy AE-180.80

Operations 2

GET /rally/matches Charted matches with shot-by-shot data (ULTRA) #
GET /rally/matches/{rallyMatchId} Rally construction for one charted match (ULTRA) #

Specifications

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-rally-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-rally-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Live Tennis Rally 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: Rally
paths:
  /rally/matches:
    get:
      summary: Charted matches with shot-by-shot data (ULTRA)
      description: 'Charted matches, newest first. RALLY CONSTRUCTION IS THE LAYER BELOW THE TAPE: the tape says what the score became after each point, this says how the point was played.


        It has its OWN id space. The charted corpus and our own match table are different populations — the corpus reaches back decades and concentrates on the biggest events, while our matches begin when our own collection did. Keying this on our match ids would hide most of it. Ask this endpoint for the authoritative coverage list rather than assuming a match is charted: charting is human work, so coverage is deep, not universal.'
      operationId: listRallyMatches
      parameters:
      - name: player
        in: query
        required: false
        schema:
          type: string
        description: Substring match on either player name.
      - name: from
        in: query
        required: false
        schema:
          type: string
          format: date
        description: '`YYYY-MM-DD`.'
      - name: to
        in: query
        required: false
        schema:
          type: string
          format: date
        description: '`YYYY-MM-DD`.'
      - name: surface
        in: query
        required: false
        schema:
          type: string
      - name: gender
        in: query
        required: false
        schema:
          type: string
          enum:
          - M
          - W
      - $ref: '#/components/parameters/limit'
      - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: Charted matches, with per-match parse-quality counts
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/RallyMatch'
                  meta:
                    $ref: '#/components/schemas/ListMeta'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/UpgradeRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Rally
  /rally/matches/{rallyMatchId}:
    get:
      summary: Rally construction for one charted match (ULTRA)
      description: One charted match with its points, in play order. Paged with `limit`/`offset`; `meta.total` is the match's full point count.
      operationId: getRallyMatch
      parameters:
      - name: rallyMatchId
        in: path
        required: true
        schema:
          type: integer
      - $ref: '#/components/parameters/limit'
      - $ref: '#/components/parameters/offset'
      responses:
        '200':
          description: The charted match with its `rally` points
          content:
            application/json:
              schema:
                allOf:
                - $ref: '#/components/schemas/RallyMatch'
                - type: object
                  properties:
                    meta:
                      $ref: '#/components/schemas/ListMeta'
                    rally:
                      type: array
                      items:
                        $ref: '#/components/schemas/RallyPoint'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/UpgradeRequired'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Rally
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
  schemas:
    RallyMatch:
      type: object
      description: One charted match of the rally-construction corpus — its own id space, keyed on `rally_match_id`.
      properties:
        rally_match_id:
          type: integer
          description: The id this product is keyed on.
        source_id:
          type: string
        match_id:
          type:
          - integer
          - 'null'
          description: OUR match id, when the charted match is also one we hold. Null otherwise — most charted matches are older than our match table.
        date:
          type:
          - string
          - 'null'
          format: date
        tournament:
          type:
          - string
          - 'null'
        round:
          type:
          - string
          - 'null'
        surface:
          type:
          - string
          - 'null'
        gender:
          type:
          - string
          - 'null'
          enum:
          - M
          - W
          - null
        best_of:
          type:
          - integer
          - 'null'
        players:
          type: array
          maxItems: 2
          items:
            type: object
            properties:
              name:
                type:
                - string
                - 'null'
              hand:
                type:
                - string
                - 'null'
                enum:
                - R
                - L
                - U
                - A
                - null
        points:
          type: integer
          description: Charted points in this match.
        points_parsed:
          type: integer
          description: How many of them our parser read cleanly — the per-match quality number.
    RallyShot:
      type: object
      description: 'One stroke. Shots are numbered from the serve: serve 1, return 2, the server''s next ball 3.'
      properties:
        number:
          type: integer
        code:
          type: string
          description: The charter's raw code, e.g. 'f'.
        stroke:
          type:
          - string
          - 'null'
          enum:
          - serve
          - groundstroke
          - slice
          - volley
          - half_volley
          - swinging_volley
          - overhead
          - drop_shot
          - lob
          - trick
          - unknown
          - null
        wing:
          type:
          - string
          - 'null'
          enum:
          - forehand
          - backhand
          - null
          description: The side it was struck FROM.
        direction:
          type:
          - string
          - 'null'
          enum:
          - forehand_side
          - middle
          - backhand_side
          - null
          description: Where the ball was sent.
        depth:
          type:
          - string
          - 'null'
          enum:
          - shallow
          - mid
          - deep
          - null
        position:
          type:
          - string
          - 'null'
          enum:
          - approaching
          - at_net
          - baseline
          - null
    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.
    RallyPoint:
      type: object
      description: One charted point. `raw` is the charter's own string, verbatim, and is ALWAYS present; the parsed fields are our reading of it. `parsed` is false when the notation contained something we could not read cleanly — the recognised part is still returned. A consumer who wants only unambiguous rows filters on `parsed`.
      properties:
        point:
          type: integer
        set:
          type: array
          items:
            type:
            - integer
            - 'null'
          maxItems: 2
        games:
          type: array
          items:
            type:
            - integer
            - 'null'
          maxItems: 2
        score:
          type:
          - string
          - 'null'
          description: e.g. '30-40'.
        game:
          type:
          - integer
          - 'null'
        is_tiebreak:
          type: boolean
        server:
          type:
          - integer
          - 'null'
          enum:
          - 1
          - 2
          - null
        point_winner:
          type:
          - integer
          - 'null'
          enum:
          - 1
          - 2
          - null
        raw:
          type:
          - string
          - 'null'
          description: The charter's shot string; both serves joined by ';' when the first was a fault.
        parsed:
          type: boolean
        serve_number:
          type:
          - integer
          - 'null'
          enum:
          - 1
          - 2
          - null
        serve_direction:
          type:
          - string
          - 'null'
          enum:
          - wide
          - body
          - down_the_t
          - null
        rally_length:
          type:
          - integer
          - 'null'
          description: Strokes including the serve. An ace is 1, a double fault 0.
        outcome:
          type:
          - string
          - 'null'
          enum:
          - winner
          - forced_error
          - unforced_error
          - error
          - other
          - null
          description: '`error` = the charter recorded a miss without saying whether it was forced. Never guessed.'
        error_location:
          type:
          - string
          - 'null'
          enum:
          - net
          - wide
          - deep
          - wide_and_deep
          - null
        ending_stroke:
          type:
          - string
          - 'null'
        ending_wing:
          type:
          - string
          - 'null'
        is_ace:
          type: boolean
        is_double_fault:
          type: boolean
        is_serve_and_volley:
          type: boolean
        is_unreturned:
          type: boolean
          description: Derived (added 2026-09-05). The server won the point without the returner putting the ball in play. A double fault is never unreturned.
        unreturned_kind:
          type:
          - string
          - 'null'
          enum:
          - ace
          - serve_winner
          - serve_forced_error
          - return_forced_error
          - return_unforced_error
          - return_error
          - null
          description: How the serve went unreturned. `serve_winner` is a serve winner charted without a direction code (so `is_ace` is unset) — count it with the aces. `return_error` = the return missed and the charter did not say whether it was forced. Null when the return was in play or the parse could not say.
        shots:
          type: array
          items:
            $ref: '#/components/schemas/RallyShot'
  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'
    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'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'Direct API key: Authorization: Bearer <key>'
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key