Live Tennis API Events API
The Events API from Live Tennis API — 1 operation(s) for events.
The Events API from Live Tennis API — 1 operation(s) for events.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/livetennisapi-com:livetennisapi-com-events-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
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: 3.2.0
info:
title: Live Tennis Events 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: Events
paths:
/events:
get:
summary: Slate-wide events feed — every match's events in one call, oldest first, cursor…
operationId: listSlateEvents
description: 'Added 2026-09-13. The rows of GET /matches/{matchId}/events for EVERY match in one request, so a poller watching the whole live slate spends one request per tick rather than one per match. `after_id` returns rows with id greater than the one passed, ascending, and `meta.next_cursor` names the last id served (null on a short page = caught up); `since` (UTC instant) is the first-call lower bound; with neither the newest page is served, still ascending. `type` narrows to a comma-separated list of event types or the family name `stoppages` (stoppage_*, pause_*, medical_timeout_*, trainer_called*, toilet_break_*). Rows carry `id` and `match_id` next to the per-match fields. Measured 2026-09-13: a scorer-stated stoppage reaches the API a median 8 s (p90 13 s) after the scorer''s own instant; the WebSocket `stoppages` signal pushes the same row as it is written.'
parameters:
- name: type
in: query
required: false
schema:
type: string
description: Comma-separated event types (see Event.type), or `stoppages` for the whole stoppage family.
example: medical_timeout_start
- name: after_id
in: query
required: false
schema:
type: integer
description: Serve rows with `id` greater than this, ascending. Take it from `meta.next_cursor` or the last row's `id`.
- name: since
in: query
required: false
schema:
type: string
format: date-time
description: First-call lower bound, a UTC instant. Rows stamped after it, ascending.
example: '2026-09-13T09:00:00Z'
- $ref: '#/components/parameters/limit'
responses:
'200':
description: Events across the slate, ascending id
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/SlateEvent'
meta:
$ref: '#/components/schemas/ListMeta'
'400':
description: bad_type, bad_after_id or bad_since
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/UpgradeRequired'
'429':
$ref: '#/components/responses/RateLimited'
tags:
- Events
components:
schemas:
Event:
type: object
properties:
type:
type: string
enum:
- break
- set_won
- game_won
- momentum_run
- stoppage_start
- stoppage_end
- pause_start
- pause_end
- medical_timeout_start
- medical_timeout_end
- trainer_called
- trainer_called_end
- toilet_break_start
- toilet_break_end
description: 'The stoppage family (added 2026-09-12). `medical_timeout_start/end`, `trainer_called/_end` and `toilet_break_start/end` are stated by the match scorer or chair umpire, with `player` = the player concerned. Coverage (2026-09-13): WTA, WTA 125, Challenger and UTR singles — WTA and WTA 125 in two layers: the match state within ~20 s (`trainer_called`, `toilet_break_*`, `stoppage_*`, player null) and a later event layer with a variable delay (measured 2026-09-13: about one minute to an hour behind play), which adds `medical_timeout_start/end` with the player and exact instants; Challenger and UTR from the scorer timeline (22 of 70 finished Challenger matches, median 8 s). ITF World Tennis Tour singles from the court''s live-scoring state (the state, not the player: `player` is null on ITF rows; first medical timeout observed and published 2026-09-13). ATP main tour: the live match stage is watched for its medical-timeout and interruption stages — present in the vocabulary, not yet observed on a tennis match, so ATP rows are mapped, not yet proven (`player` null, `at` = our observation instant). Not doubles. `stoppage_start/end` is a whole-match stop (`reason` weather | other when stated, unknown when only a bare suspension flag reached us). `pause_start/end` is an interruption of play measured from our own point clocks (`basis: inferred`), never labelled medical.'
player:
type:
- integer
- 'null'
enum:
- 1
- 2
- null
timestamp:
type:
- string
- 'null'
format: date-time
at:
type:
- string
- 'null'
format: date-time
description: Stoppage family — the UTC instant the stoppage happened (for a pause, its last score change).
detected_at:
type:
- string
- 'null'
format: date-time
description: Stoppage family — when we recorded it.
basis:
type:
- string
- 'null'
enum:
- observed
- inferred
- null
description: Stoppage family — observed (a source stated it) or inferred (measured from our clocks).
reason:
type:
- string
- 'null'
description: Stoppage family — medical_timeout | unspecified | no_treatment | taping | changeover_treatment | evaluating | failed | toilet_break | weather | other | resumed | unknown.
position:
type:
- string
- 'null'
enum:
- point
- changeover
- setbreak
- null
description: pause rows only — where the last point left the match, which sets the silence threshold.
score:
type:
- object
- 'null'
description: Stoppage family — sets, games, points, server at the moment of the stoppage.
duration_seconds:
type:
- integer
- 'null'
description: end rows only.
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.
SlateEvent:
description: An Event as served by the slate-wide feed (GET /events) — the per-match row plus its id and match.
allOf:
- $ref: '#/components/schemas/Event'
- type: object
properties:
id:
type: integer
description: The event's id; the `after_id` cursor.
match_id:
type: integer
parameters:
limit:
name: limit
in: query
schema:
type: integer
default: 50
minimum: 1
maximum: 200
responses:
UpgradeRequired:
description: Your tier doesn't unlock this endpoint
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