Documentation
¶
Overview ¶
Package livetennisapi is the official Go client for the Live Tennis API: real-time tennis scores, player data, point-by-point tapes, deep historical results back to 1968, head-to-head records, point-in-time rankings, shot-level charting, match-winner market prices, and model-driven match analysis — across ATP, WTA, Challenger, ITF and juniors. The API is read-only.
Getting started ¶
Create a client with your key and call it with a context.Context:
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
page, err := client.ListMatches(ctx, livetennisapi.ListMatchesParams{
Status: livetennisapi.StatusLive,
Tour: livetennisapi.TourATP,
ListParams: livetennisapi.ListParams{Limit: 10},
})
if err != nil {
return err
}
for _, m := range page.Data {
fmt.Println(m.Tournament, m.Score)
}
A free key is self-serve at https://livetennisapi.com/subscribe/free. The key travels as a Bearer token by default; WithAuthMethod switches it to the X-API-Key header, which the API accepts equally.
Tiers ¶
Access is tiered FREE, BASIC, PRO, ULTRA. FREE covers live and upcoming matches, scores, players, fixtures, the tournament catalogue and your own usage. Historical results — completed matches, per-match tapes, the 1968–2022 archive and head-to-head records — need BASIC. Match events, market prices, bulk history packages and the rankings listing need PRO. Model analysis, the live model fields (Score.WinProbabilityP1, Score.Danger), in-play statistics, rally and charting data, per-player rankings, the push-feed token and webhooks need ULTRA. Calling above your tier returns 403, which this package surfaces as ErrUpgradeRequired:
analysis, err := client.GetMatchAnalysis(ctx, matchID)
if errors.Is(err, livetennisapi.ErrUpgradeRequired) {
// Valid key, insufficient plan — not an authentication failure.
}
That distinction matters: a tier wall proves the key works. Use ErrUnauthorized for a key problem and ErrUpgradeRequired for a plan problem, and never treat one as the other.
Errors ¶
Every non-2xx response becomes an *APIError carrying the status, the API's machine-readable code, and the rate-limit budget observed on that response. Match the common cases with errors.Is against the package sentinels (ErrBadRequest, ErrUnauthorized, ErrUpgradeRequired, ErrNotFound, ErrRateLimited, ErrServerError, ErrServiceUnavailable), and reach for the concrete type with errors.As when you need the detail:
var apiErr *livetennisapi.APIError
if errors.As(err, &apiErr) {
log.Printf("%d %s, %d requests left", apiErr.StatusCode, apiErr.Code,
apiErr.RateLimit.RemainingOr(-1))
}
A request that never produced a response — DNS, TLS, refused, timed out, context cancelled — becomes a *ConnectionError instead, matching ErrConnection and, for a deadline, ErrTimeout.
Nullability ¶
The API distinguishes "absent" from zero, and so does this package, because a rank of 0 or a price of 0 looks meaningful and is not. The rule is:
- Nullable numbers and booleans are pointers (Player.Ranking, Score.Server, Match.Winner, Price.Bid). nil means the API sent null or omitted the field.
- Nullable strings are plain strings, where "" already means absent unambiguously (Player.Country, Match.Surface).
- Nullable timestamps are Time, whose zero value means absent; test it with IsZero.
- A whole absent object is a nil pointer. In particular Match.Score is nil for an upcoming match that has not started, so always check it before dereferencing.
Tours ¶
Client.ListMatches and Client.ListFixtures take an optional Tour filter, each value covering that circuit's singles and doubles draws. An unrecognised tour is refused with ErrBadRequest and code "bad_tour", and the accepted values are then on APIError.AllowedValues rather than buried in the raw body.
Mind that the filter's vocabulary is narrower than the one player and fixture records use: filtering by TourJuniors returns records whose own tour field reads "juniors_boys" or "juniors_girls". Player.Tour and Fixture.Tour are therefore plain strings, and comparing one against a Tour constant will silently fail to match. Match.Tour is the exception: it shares the filter's own vocabulary, so it is typed Tour and safe to compare and group on.
Forward compatibility ¶
The API ships additive changes within v1. Fields this package does not know about are ignored rather than rejected, so a server-side addition never breaks an older client. Treat every field as optional. Player gained Player.DataCompleteness this way, which reports how much of a player's biography the feed actually holds.
Concurrency ¶
A Client is safe for concurrent use by multiple goroutines, and is meant to be created once and shared. It holds no per-request state.
Index ¶
- Constants
- Variables
- func Paginate[T any](ctx context.Context, fetch func(context.Context, ListParams) (*Page[T], error), ...) iter.Seq2[T, error]
- type APIError
- type Analysis
- type ArchiveCareer
- type ArchiveCareerRecord
- type ArchiveCareerServe
- type ArchiveMatch
- type ArchiveMatchStats
- type ArchiveMatchesParams
- type ArchivePlayer
- type ArchivePlayerBio
- type ArchivePlayersParams
- type ArchiveServeStats
- type ArchiveYearRecord
- type AuthMethod
- type ChartingMatch
- type ChartingPlayer
- type ChartingPlayerParams
- type Client
- func (c *Client) CreateWebhook(ctx context.Context, params WebhookParams) (*Webhook, error)
- func (c *Client) DeleteWebhook(ctx context.Context, webhookID int64) error
- func (c *Client) DownloadHistoryPackage(ctx context.Context, period string, kind PackageKind, format string) (io.ReadCloser, error)
- func (c *Client) GetArchiveCareer(ctx context.Context, name string) (*ArchiveCareer, error)
- func (c *Client) GetArchiveMatch(ctx context.Context, archiveID int64) (*ArchiveMatch, error)
- func (c *Client) GetChartingMatch(ctx context.Context, chartingMatchID int64) (*ChartingMatch, error)
- func (c *Client) GetChartingPlayer(ctx context.Context, params ChartingPlayerParams) (*ChartingPlayer, error)
- func (c *Client) GetHeadToHead(ctx context.Context, p1, p2 string) (*HeadToHead, error)
- func (c *Client) GetHistoryPackage(ctx context.Context, period string, kind PackageKind) (*HistoryPackage, error)
- func (c *Client) GetMarketPrices(ctx context.Context, matchID int64, params ListParams) (*Market, error)
- func (c *Client) GetMatch(ctx context.Context, matchID int64) (*Match, error)
- func (c *Client) GetMatchAnalysis(ctx context.Context, matchID int64) (*Analysis, error)
- func (c *Client) GetMatchRally(ctx context.Context, matchID int64, params ListParams) (*RallyMatchDetail, error)
- func (c *Client) GetMatchScore(ctx context.Context, matchID int64) (*Score, error)
- func (c *Client) GetMatchStatistics(ctx context.Context, matchID int64) (*MatchStatistics, error)
- func (c *Client) GetMatchTape(ctx context.Context, matchID int64, params TapeParams) (*MatchTape, error)
- func (c *Client) GetPlayer(ctx context.Context, playerID int64) (*Player, error)
- func (c *Client) GetRallyMatch(ctx context.Context, rallyMatchID int64, params ListParams) (*RallyMatchDetail, error)
- func (c *Client) GetTournament(ctx context.Context, tournamentID string) (*Tournament, error)
- func (c *Client) GetUsage(ctx context.Context) (*Usage, error)
- func (c *Client) GetWSToken(ctx context.Context) (*WSToken, error)
- func (c *Client) Health(ctx context.Context) (*Health, error)
- func (c *Client) ListArchiveMatches(ctx context.Context, params ArchiveMatchesParams) (*Page[ArchiveMatch], error)
- func (c *Client) ListArchivePlayers(ctx context.Context, params ArchivePlayersParams) (*Page[ArchivePlayerBio], error)
- func (c *Client) ListCompletedMatches(ctx context.Context, params ListParams) (*Page[Match], error)
- func (c *Client) ListFixtures(ctx context.Context, params ListFixturesParams) (*Page[Fixture], error)
- func (c *Client) ListHistoryMatches(ctx context.Context, params HistoryMatchesParams) (*Page[Match], error)
- func (c *Client) ListHistoryPackages(ctx context.Context, params HistoryPackagesParams) (*HistoryPackagesPage, error)
- func (c *Client) ListMarkets(ctx context.Context, matchID int64) (*Page[Market], error)
- func (c *Client) ListMatchEvents(ctx context.Context, matchID int64, params ListParams) (*Page[Event], error)
- func (c *Client) ListMatchPrices(ctx context.Context, matchID int64, params MatchPricesParams) (*MatchPrices, error)
- func (c *Client) ListMatches(ctx context.Context, params ListMatchesParams) (*Page[Match], error)
- func (c *Client) ListRallyMatches(ctx context.Context, params RallyMatchesParams) (*Page[RallyMatch], error)
- func (c *Client) ListRankings(ctx context.Context, params RankingsParams) (*RankingsPage, error)
- func (c *Client) ListTournaments(ctx context.Context, params TournamentsParams) (*Page[Tournament], error)
- func (c *Client) ListWebhooks(ctx context.Context) (*Page[Webhook], error)
- func (c *Client) SearchPlayers(ctx context.Context, params SearchPlayersParams) (*Page[Player], error)
- type ConnectionError
- type Coverage
- type DataCompleteness
- type Event
- type EventType
- type Fixture
- type H2HMeeting
- type H2HSurfaceSplit
- type H2HTotals
- type HeadToHead
- type Health
- type HistoryMatchesParams
- type HistoryPackage
- type HistoryPackagesPage
- type HistoryPackagesParams
- type ListFixturesParams
- type ListMatchesParams
- type ListMeta
- type ListParams
- type Market
- type Match
- type MatchPrices
- type MatchPricesMeta
- type MatchPricesParams
- type MatchStatistics
- type MatchStatus
- type MatchTape
- type MeasuredDivergence
- type Option
- type PackageFile
- type PackageKind
- type Page
- type Player
- type PlayerStats
- type Players
- type Price
- type Profile
- type RallyMatch
- type RallyMatchDetail
- type RallyMatchesParams
- type RallyPlayerRef
- type RallyPoint
- type RallyShot
- type RankingRecord
- type RankingSystem
- type RankingsCoverage
- type RankingsMeta
- type RankingsPage
- type RankingsParams
- type RateLimit
- type Score
- type SearchPlayersParams
- type Sequence
- type SetTiebreak
- type StatisticsDescribes
- type StatisticsFamily
- type StatisticsFreshness
- type StatisticsSide
- type TapeInfo
- type TapeMeta
- type TapeParams
- type TapeRow
- type Thesis
- type ThesisNotes
- type Tier
- type Time
- type Tour
- type Tournament
- type TournamentsParams
- type Usage
- type UsageDay
- type WSChannels
- type WSToken
- type Webhook
- type WebhookEvent
- type WebhookParams
- type WinLoss
Examples ¶
Constants ¶
const ( // Version is this client's version, reported in the User-Agent. Version = "1.2.1" // DefaultBaseURL is the production API root. DefaultBaseURL = "https://api.livetennisapi.com/api/public/v1" // DefaultTimeout is the per-request timeout of the default HTTP client. DefaultTimeout = 30 * time.Second // DefaultMaxRetries is how many times a retryable failure is retried. DefaultMaxRetries = 2 // MaxLimit is the largest page size the API accepts. MaxLimit = 200 // MaxPriceTicks is the largest tick count [Client.ListMatchPrices] // accepts — that endpoint has its own, higher cap in place of pagination. MaxPriceTicks = 500 )
Variables ¶
var ( // ErrAPI matches every error this package returns, transport failures // included. Useful to tell "the tennis API failed" from an unrelated error // further up your own stack. ErrAPI = errors.New("livetennisapi: request failed") // ErrBadRequest is a 400: a query parameter was malformed. ErrBadRequest = errors.New("livetennisapi: bad request") // This is a credential problem, never a plan problem. ErrUnauthorized = errors.New("livetennisapi: unauthorized") // ErrUpgradeRequired is a 403: the endpoint exists and your key is valid, // but your tier does not unlock it. Treating this as an auth failure is // the classic mistake — a 403 proves the key works. ErrUpgradeRequired = errors.New("livetennisapi: upgrade required") // ErrNotFound is a 404: no such resource, or no data for it yet. Analysis // and market endpoints return it for a match the model has not covered. ErrNotFound = errors.New("livetennisapi: not found") // ErrWebhookLimit is a 409: the key already holds its maximum of 3 // webhooks, so registration was refused. Delete one with // [Client.DeleteWebhook] first. ErrWebhookLimit = errors.New("livetennisapi: webhook limit reached") // ErrRateLimited is a 429: the tier's rate-limit window was exceeded. // [APIError.RateLimit] carries how long to wait. ErrRateLimited = errors.New("livetennisapi: rate limited") // ErrServerError is any 5xx: the API failed to serve the request. ErrServerError = errors.New("livetennisapi: server error") ErrServiceUnavailable = errors.New("livetennisapi: service unavailable") // ErrConnection means the request never produced a response — DNS, TLS, // connection refused, or a cancelled context. ErrConnection = errors.New("livetennisapi: connection failed") // ErrTimeout means the request exceeded its deadline. It also matches // ErrConnection, since no response was produced either way. ErrTimeout = errors.New("livetennisapi: request timed out") )
Sentinel errors for the cases worth branching on. Compare with errors.Is rather than by value, since the concrete error is always an *APIError or a *ConnectionError that reports itself as matching these:
if errors.Is(err, livetennisapi.ErrUpgradeRequired) { ... }
ErrServerError matches any 5xx, so a 503 satisfies both it and ErrServiceUnavailable — the same containment the Python and JS clients get from subclassing.
Functions ¶
func Paginate ¶
func Paginate[T any]( ctx context.Context, fetch func(context.Context, ListParams) (*Page[T], error), pageSize int, ) iter.Seq2[T, error]
Paginate walks every page of a list endpoint and yields the items one at a time, fetching the next page only as the range loop asks for it.
fetch is called with the pagination to apply, which lets it close over whatever other filters the endpoint takes:
seq := livetennisapi.Paginate(ctx,
func(ctx context.Context, p livetennisapi.ListParams) (*livetennisapi.Page[livetennisapi.Player], error) {
return client.SearchPlayers(ctx, livetennisapi.SearchPlayersParams{Search: "nadal", ListParams: p})
}, 0)
for player, err := range seq {
if err != nil {
return err
}
fmt.Println(player.Name)
}
An error is yielded once, with the zero item, and then iteration stops — so a loop that ignores err would spin on nothing rather than forever. Always check it.
pageSize is clamped to 1..MaxLimit; pass 0 for the largest page allowed, which minimises requests against your rate limit. Iteration ends when the API says has_more is false, and otherwise on the first short page — ListMeta.Count describes the page, not the collection, so it is never the signal. The has_more flag matters on the coverage-filtered history listing, where the filter is applied after the page is cut and a filtered page is routinely shorter than requested while later pages still hold matches.
Example ¶
Paginate walks a whole collection, fetching each page only as it is needed.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
players := livetennisapi.Paginate(context.Background(),
func(ctx context.Context, p livetennisapi.ListParams) (*livetennisapi.Page[livetennisapi.Player], error) {
return client.SearchPlayers(ctx, livetennisapi.SearchPlayersParams{
Search: "nadal",
ListParams: p,
})
}, 0)
for player, err := range players {
if err != nil {
log.Fatal(err)
}
fmt.Println(player.Name)
}
}
Output:
Types ¶
type APIError ¶
type APIError struct {
// StatusCode is the HTTP status.
StatusCode int
// Code is the API's machine-readable error code, taken from the response
// body's "error" field — for example "unauthorized" or "upgrade_required".
// Empty when the body carried no usable code.
Code string
// Message is a human-readable summary: the API's code when it sent one,
// otherwise the HTTP status text.
Message string
// Detail is the API's human-readable explanation, from the body's
// "detail" field, when one adds anything. Empty otherwise.
Detail string
// RateLimit is the budget the API reported on this response.
RateLimit RateLimit
// ResetsAt is when the daily quota resets, from the "resets_at" field of
// a daily 429 (Code "rate_limited" with Scope "day"). It is an absolute
// instant derived from the account's local midnight — do not assume any
// fixed UTC hour. Zero on every other error.
ResetsAt time.Time
// Scope is the rate-limit window that was exhausted on a 429: "day" for
// the daily quota, empty for the per-minute window (which carries no
// scope) and for every non-429 error.
Scope string
// LimitPerDay is the daily quota that was exhausted, from the
// "limit_per_day" field of a daily 429. nil elsewhere.
LimitPerDay *int
// RetryAt is when an abuse throttle lifts, from the "retry_at_epoch"
// field of a 429 with Code "abuse_throttled" — the block the API places
// on chronically over-cap clients for around 24 hours. If you see it,
// fix the retry loop that earned it rather than waiting it out. Zero on
// every other error.
RetryAt time.Time
// RequiredTier is the lowest tier that unlocks the endpoint, inferred from
// the path on a 403. Empty on every other status, and on a 403 from an
// endpoint that needs no upgrade.
RequiredTier Tier
// URL is the request URL, with the API key never included (the key travels
// in a header, not the query string).
URL string
// Header is the full response header, for anything this struct does not
// model. May be nil.
Header http.Header
// Body is the raw response body, kept verbatim so a payload this package
// failed to model is still available. May be nil.
Body []byte
// AllowedValues lists the values the API would have accepted, when it says
// so. Rejecting a tour answers 400 with
// {"error":"bad_tour","allowed":["atp","challenger","itf","juniors","wta"]}
// and this is that list. nil when the response named no alternatives.
AllowedValues []string
// Candidates lists the players an ambiguous name fragment matched, from
// the "candidates" field of a 400 with Code "ambiguous_name" on the
// head-to-head and archive-career endpoints — the API refuses to sum two
// people into one record. nil elsewhere.
Candidates []string
}
APIError is a non-2xx response from the API.
Branch on it with errors.Is against the package sentinels for the common cases, and pull it out with errors.As when you need the detail:
var apiErr *livetennisapi.APIError
if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound {
// no data for that match yet
}
Example (AllowedValues) ¶
An unrecognised tour is rejected rather than ignored, and the error names the values that would have worked.
package main
import (
"context"
"errors"
"fmt"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
_, err := client.ListMatches(context.Background(), livetennisapi.ListMatchesParams{
Tour: livetennisapi.Tour("atpp"),
})
var apiErr *livetennisapi.APIError
if errors.As(err, &apiErr) && apiErr.Code == "bad_tour" {
fmt.Println("allowed:", apiErr.AllowedValues)
}
}
Output:
type Analysis ¶
type Analysis struct {
// Thesis is the directional call, or nil if the model has none.
Thesis *Thesis `json:"thesis,omitempty"`
// Profile is the pre-match shape of the contest, or nil.
Profile *Profile `json:"profile,omitempty"`
}
Analysis is the model's read on a match. ULTRA only.
Either half may be nil: the model does not cover every match, and a match it has not analysed returns a payload with both halves null rather than a 404.
type ArchiveCareer ¶ added in v1.1.0
type ArchiveCareer struct {
// Player carries the resolved name.
Player struct {
Name string `json:"name,omitempty"`
} `json:"player,omitzero"`
// Span is the first and last tournament dates of the career.
Span struct {
First Time `json:"first,omitzero"`
Last Time `json:"last,omitzero"`
} `json:"span,omitzero"`
// Record is the W-L record, overall and split.
Record ArchiveCareerRecord `json:"record,omitzero"`
// ByYear is the season-by-season record.
ByYear []ArchiveYearRecord `json:"by_year,omitempty"`
// Serve is the summed serve-stat block.
Serve ArchiveCareerServe `json:"serve,omitzero"`
}
ArchiveCareer is one player's whole archive career in one response. Everything is a sum or a ratio of sums over rows you can fetch individually through Client.ListArchiveMatches — nothing is modelled.
type ArchiveCareerRecord ¶ added in v1.1.0
type ArchiveCareerRecord struct {
Wins int `json:"wins,omitempty"`
Losses int `json:"losses,omitempty"`
// Titles is finals won, excluding abandoned finals.
Titles int `json:"titles,omitempty"`
// BySurface splits the record by surface name.
BySurface map[string]WinLoss `json:"by_surface,omitempty"`
// ByLevel splits the record by source tier code (see
// [ArchiveMatch.Level]).
ByLevel map[string]WinLoss `json:"by_level,omitempty"`
}
ArchiveCareerRecord is the W-L record of an archive career.
type ArchiveCareerServe ¶ added in v1.1.0
type ArchiveCareerServe struct {
MatchesWithStats int `json:"matches_with_stats,omitempty"`
Aces int `json:"aces,omitempty"`
DoubleFaults int `json:"double_faults,omitempty"`
ServePoints int `json:"serve_points,omitempty"`
FirstIn int `json:"first_in,omitempty"`
FirstWon int `json:"first_won,omitempty"`
SecondWon int `json:"second_won,omitempty"`
ServeGames int `json:"serve_games,omitempty"`
BPSaved int `json:"bp_saved,omitempty"`
BPFaced int `json:"bp_faced,omitempty"`
FirstInPct *float64 `json:"first_in_pct,omitempty"`
FirstWonPct *float64 `json:"first_won_pct,omitempty"`
SecondWonPct *float64 `json:"second_won_pct,omitempty"`
BPSavedPct *float64 `json:"bp_saved_pct,omitempty"`
AcesPerMatch *float64 `json:"aces_per_match,omitempty"`
}
ArchiveCareerServe is the summed serve-stat block of an archive career, with derived ratios. The corpus records per-match serve statistics from 1991 only; MatchesWithStats states the coverage honestly. Ratio fields are nil where the denominator is zero.
type ArchiveMatch ¶ added in v1.1.0
type ArchiveMatch struct {
// ID is the archive record's id, usable with [Client.GetArchiveMatch].
ID int64 `json:"id,omitempty"`
// SourceID is the stable corpus key.
SourceID string `json:"source_id,omitempty"`
// Tour is "atp" or "wta" — the archive covers only those two.
Tour string `json:"tour,omitempty"`
// Level is the source tier code: "G" grand slam, "M" masters, "A" tour,
// "F" finals, "D" Davis Cup, "C" challenger, "O" olympics; futures tiers
// carry their category codes (e.g. "15", "25") as published.
Level string `json:"level,omitempty"`
// Tournament is the event name. Empty when unknown.
Tournament string `json:"tournament,omitempty"`
// Surface is the court surface. Empty when unknown.
Surface string `json:"surface,omitempty"`
// DrawSize is the draw size. nil when unrecorded.
DrawSize *int `json:"draw_size,omitempty"`
// EventDate is the TOURNAMENT START date — per-match dates do not exist
// in this era's records. Zero if absent.
EventDate Time `json:"event_date,omitzero"`
// Round is the round code, e.g. "F", "SF", "R32". Empty when unknown.
Round string `json:"round,omitempty"`
// BestOf is 3 or 5. nil when unrecorded.
BestOf *int `json:"best_of,omitempty"`
// Minutes is the match duration in minutes. nil when unrecorded.
Minutes *int `json:"minutes,omitempty"`
// Winner is the winning side.
Winner *ArchivePlayer `json:"winner,omitempty"`
// Loser is the losing side.
Loser *ArchivePlayer `json:"loser,omitempty"`
// Score is the final score as published, e.g. "6-4 7-6(5)", "6-3 RET",
// "W/O". Empty when unknown.
Score string `json:"score,omitempty"`
// Outcome is parsed from the score's own vocabulary: "completed",
// "retired", "walkover", "default" or "abandoned". Empty when
// unparseable — never guessed.
Outcome string `json:"outcome,omitempty"`
// Stats holds both sides' serve statistics where the source recorded
// them. Populated by [Client.GetArchiveMatch] only, and nil for the
// (mostly pre-1991) rows the source never recorded statistics for —
// never synthesised.
Stats *ArchiveMatchStats `json:"stats,omitempty"`
}
ArchiveMatch is one result from the deep historical corpus, 1968–2022.
It is winner/loser-shaped because results data is recorded that way at the source — the winner is a stored column, never an inference. The archive is its own id space, separate from /matches, and ends where the API's own point-by-point coverage begins (2023-01), so no match is ever served from two datasets.
type ArchiveMatchStats ¶ added in v1.1.0
type ArchiveMatchStats struct {
Winner *ArchiveServeStats `json:"winner,omitempty"`
Loser *ArchiveServeStats `json:"loser,omitempty"`
}
ArchiveMatchStats is the per-match statistics block on an archive result.
type ArchiveMatchesParams ¶ added in v1.1.0
type ArchiveMatchesParams struct {
// Tour is [TourATP] or [TourWTA] — the archive covers only those two.
// Empty means both.
Tour Tour
// Name is a case-insensitive substring match on EITHER player's name,
// minimum 3 characters.
Name string
// From and To bound the tournament start date, "YYYY-MM-DD".
From string
To string
// Round filters by round code: "F", "SF", "QF", "R16" through "R128",
// "RR", "BR", "Q1" to "Q4", "ER".
Round string
// Level filters by source tier code (see [ArchiveMatch.Level]).
Level string
// ListParams paginates the result.
ListParams
}
ArchiveMatchesParams filters Client.ListArchiveMatches.
type ArchivePlayer ¶ added in v1.1.0
type ArchivePlayer struct {
// Name is the player's name. Empty when unknown.
Name string `json:"name,omitempty"`
// Hand is the playing hand, "R" or "L". Empty when unknown.
Hand string `json:"hand,omitempty"`
// Country is the 3-letter code, in the same vocabulary as
// [Player.Country]. Empty when unknown.
Country string `json:"country,omitempty"`
// Rank is the player's rank AT THE TIME of the match, as published. nil
// when unranked or unrecorded.
Rank *int `json:"rank,omitempty"`
// Seed is the draw seed. nil for an unseeded player.
Seed *int `json:"seed,omitempty"`
// PlayerID is the corpus person id, which joins
// [Client.ListArchivePlayers] within the same tour. NOT a roster player
// id — the archive is a separate id space. nil when unknown.
PlayerID *int64 `json:"player_id,omitempty"`
// HeightCm is the height in centimetres. nil when unrecorded.
HeightCm *int `json:"height_cm,omitempty"`
// Age is the age at the time of the match, as the corpus records it. nil
// when unrecorded.
Age *float64 `json:"age,omitempty"`
// Entry is the draw entry where recorded — "WC", "Q", "LL", "PR", "SE"
// and the like. Empty for direct acceptances.
Entry string `json:"entry,omitempty"`
}
ArchivePlayer is one side of an archive result, as the corpus recorded it at the time of the match.
type ArchivePlayerBio ¶ added in v1.1.0
type ArchivePlayerBio struct {
// ID is the corpus person id.
ID int64 `json:"id,omitempty"`
// Tour is "atp" or "wta".
Tour string `json:"tour,omitempty"`
// Name is the player's name. Empty when unknown.
Name string `json:"name,omitempty"`
// Hand is the playing hand. Empty when unknown.
Hand string `json:"hand,omitempty"`
// DOB is the date of birth. Zero when unknown.
DOB Time `json:"dob,omitzero"`
// Country is the 3-letter code. Empty when unknown.
Country string `json:"country,omitempty"`
// HeightCm is the height in centimetres. nil when unrecorded.
HeightCm *int `json:"height_cm,omitempty"`
// CareerHighRank is the best rank reached, computed offline from the
// corpus's own weekly ranking tables. nil when never ranked or
// unrecorded.
CareerHighRank *int `json:"career_high_rank,omitempty"`
// CareerHighDate is the earliest week the career-high rank was reached.
// Zero when unknown.
CareerHighDate Time `json:"career_high_date,omitzero"`
}
ArchivePlayerBio is one person of the results archive, in the archive's own id space: ID is the corpus person id that archive match rows carry as ArchivePlayer.PlayerID, scoped per tour — never a roster id. Null fields are the era's silence, never guessed.
type ArchivePlayersParams ¶ added in v1.1.0
type ArchivePlayersParams struct {
// Name is a case-insensitive substring filter, minimum 3 characters.
Name string
// Tour is [TourATP] or [TourWTA]. Empty means both.
Tour Tour
// ListParams paginates the result.
ListParams
}
ArchivePlayersParams filters Client.ListArchivePlayers.
type ArchiveServeStats ¶ added in v1.1.0
type ArchiveServeStats struct {
Aces *int `json:"aces,omitempty"`
DoubleFaults *int `json:"double_faults,omitempty"`
ServePoints *int `json:"serve_points,omitempty"`
FirstIn *int `json:"first_in,omitempty"`
FirstWon *int `json:"first_won,omitempty"`
SecondWon *int `json:"second_won,omitempty"`
ServeGames *int `json:"serve_games,omitempty"`
BPSaved *int `json:"bp_saved,omitempty"`
BPFaced *int `json:"bp_faced,omitempty"`
}
ArchiveServeStats is one side's per-match serve statistics, where the era recorded them.
type ArchiveYearRecord ¶ added in v1.1.0
type ArchiveYearRecord struct {
Year int `json:"year,omitempty"`
Wins int `json:"wins,omitempty"`
Losses int `json:"losses,omitempty"`
}
ArchiveYearRecord is one season of an archive career.
type AuthMethod ¶
type AuthMethod int
AuthMethod selects the header that carries the API key.
const ( // AuthBearer sends the key as "Authorization: Bearer <key>". This is the // default, matching the official Python and JS clients. AuthBearer AuthMethod = iota // AuthAPIKey sends the key as "X-API-Key: <key>" instead. The API accepts // either; use this when an intermediary strips or rewrites Authorization // headers. AuthAPIKey )
type ChartingMatch ¶ added in v1.1.0
type ChartingMatch struct {
// ChartingMatchID is the charting product's id.
ChartingMatchID int64 `json:"charting_match_id,omitempty"`
// MCPID is the Match Charting Project's own id string.
MCPID string `json:"mcp_id,omitempty"`
// Gender is "M" or "W". Empty when unknown.
Gender string `json:"gender,omitempty"`
// Players identifies both players, as raw JSON.
Players json.RawMessage `json:"players,omitempty"`
// Families maps stat-family names to per-player, per-set stat rows, as
// raw JSON — the v1 schema does not pin their shape.
Families json.RawMessage `json:"families,omitempty"`
}
ChartingMatch is one charted match with every Match Charting Project stat family for both players, with the per-set split (rows "1", "2", "Total") exactly as charted. Its id space is the charting product's own, mostly matches with no counterpart in the live table.
type ChartingPlayer ¶ added in v1.1.0
type ChartingPlayer struct {
// Player identifies the resolved player, as raw JSON — the v1 schema
// does not pin its shape.
Player json.RawMessage `json:"player,omitempty"`
// MatchesCharted is how many charted matches the sums cover.
MatchesCharted int `json:"matches_charted,omitempty"`
// Coverage describes the sample in prose.
Coverage string `json:"coverage,omitempty"`
// Families maps stat-family names to their summed numeric columns, as
// raw JSON. Every field is a raw SUM over the player's Total rows.
Families json.RawMessage `json:"families,omitempty"`
}
ChartingPlayer is one player's career shot-level charting aggregate, from the Match Charting Project: serve placement, return depth and outcomes, net and serve-and-volley conversion, clutch serving and returning, winners and unforced errors by wing, and rally-length and shot-direction tendencies — summed over the player's charted matches.
Coverage is curated, not full-slate: 11,646 charted matches across both tours back to the 1960s, concentrated on the majors. MatchesCharted states the sample.
type ChartingPlayerParams ¶ added in v1.1.0
type ChartingPlayerParams struct {
// Name is the player name, minimum 3 characters. A fragment matching
// more than one charted person is refused with the candidates.
Name string
// Gender is "men" or "women", to disambiguate a name that appears on
// both tours. Empty when not needed. (Mind the vocabulary: this endpoint
// takes "men"/"women" where the rally listing takes "M"/"W".)
Gender string
}
ChartingPlayerParams identifies the player for Client.GetChartingPlayer.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a Live Tennis API client.
Create one with New and share it: it is safe for concurrent use, holds no per-request state, and reuses connections through its HTTP client.
func New ¶
New returns a client authenticating with apiKey.
Get a free key at https://livetennisapi.com/subscribe/free. The key is read only from this argument: pull it from the environment yourself so your configuration stays explicit.
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
An empty key is allowed, because Client.Health needs none. Every other endpoint will then fail with ErrUnauthorized.
Example ¶
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
page, err := client.ListMatches(context.Background(), livetennisapi.ListMatchesParams{
Status: livetennisapi.StatusLive,
ListParams: livetennisapi.ListParams{Limit: 10},
})
if err != nil {
log.Fatal(err)
}
for _, match := range page.Data {
fmt.Printf("%s: %s vs %s — %s\n",
match.Tournament,
match.Players.P1.Name,
match.Players.P2.Name,
match.Score, // nil-safe: prints "-" before a match starts
)
}
}
Output:
func (*Client) CreateWebhook ¶ added in v1.2.0
CreateWebhook registers an outbound webhook: the API POSTs the same frames the push WebSocket sends to the given HTTPS endpoint on every matching commit. ULTRA, and DIRECT KEYS ONLY — a marketplace key is refused with a 403 carrying code "direct_key_required", which is a channel restriction, not a tier one.
The response is the ONLY time Webhook.Secret is shown — store it immediately. A key holds at most 3 webhooks; the 4th registration is refused with ErrWebhookLimit, so delete one first.
Registration is never retried automatically: a request that timed out may still have been applied, and re-sending it could register a duplicate.
func (*Client) DeleteWebhook ¶ added in v1.2.0
DeleteWebhook removes one of your webhooks. ULTRA, direct keys only. It returns ErrNotFound for an id that is not yours or is already gone.
Deletion is never retried automatically, so a timeout is reported rather than papered over — re-check with Client.ListWebhooks before assuming either outcome.
func (*Client) DownloadHistoryPackage ¶ added in v1.2.0
func (c *Client) DownloadHistoryPackage(ctx context.Context, period string, kind PackageKind, format string) (io.ReadCloser, error)
DownloadHistoryPackage streams one monthly package file. format is "jsonl" (one whole tape object per line, coverage meta included) or "csv" (one row per point, no coverage columns); an unknown format is rejected with ErrBadRequest. Same gating as Client.ListHistoryPackages.
The caller owns the returned body and must Close it. Package files run to hundreds of megabytes, which is why this streams rather than buffering — verify what you stored against the manifest's SHA-256 from Client.GetHistoryPackage.
func (*Client) GetArchiveCareer ¶ added in v1.1.0
GetArchiveCareer returns one player's whole archive career in one response: W-L record overall, by surface, by level and by year, titles, and the summed serve-stat block with derived ratios. BASIC, or any History plan.
name is a fragment of at least 3 characters and must resolve to one person; an ambiguous fragment is refused with the candidates, the same rule as Client.GetHeadToHead.
func (*Client) GetArchiveMatch ¶ added in v1.1.0
GetArchiveMatch returns one archive result with serve statistics where the era recorded them: ArchiveMatch.Stats is nil for the (mostly pre-1991) rows the source never recorded statistics for — never synthesised. BASIC, or any History plan.
func (*Client) GetChartingMatch ¶ added in v1.1.0
func (c *Client) GetChartingMatch(ctx context.Context, chartingMatchID int64) (*ChartingMatch, error)
GetChartingMatch returns one charted match with every stat family for both players, per-set split included. ULTRA — below that it returns ErrUpgradeRequired.
func (*Client) GetChartingPlayer ¶ added in v1.1.0
func (c *Client) GetChartingPlayer(ctx context.Context, params ChartingPlayerParams) (*ChartingPlayer, error)
GetChartingPlayer returns one player's career shot-level charting aggregate — the deepest serve/return profile the API holds. ULTRA — below that it returns ErrUpgradeRequired.
func (*Client) GetHeadToHead ¶ added in v1.1.0
GetHeadToHead returns the record between two players across both halves of the product: the results archive (1968–2022) and the API's own completed matches (2023 onward). BASIC, or any History plan — below that it returns ErrUpgradeRequired. On ULTRA the response additionally carries the HeadToHead.Stats aggregate block.
p1 and p2 are name fragments of at least 3 characters. A fragment matching more than one player is refused with ErrBadRequest, code "ambiguous_name" and the candidate list on APIError.AllowedValues — because two people summed into one record would be a wrong answer, not a convenience.
func (*Client) GetHistoryPackage ¶ added in v1.2.0
func (c *Client) GetHistoryPackage(ctx context.Context, period string, kind PackageKind) (*HistoryPackage, error)
GetHistoryPackage returns one monthly package's manifest — its files with sizes and SHA-256 checksums. Same gating as Client.ListHistoryPackages. The file itself streams through Client.DownloadHistoryPackage.
func (*Client) GetMarketPrices ¶
func (c *Client) GetMarketPrices(ctx context.Context, matchID int64, params ListParams) (*Market, error)
GetMarketPrices returns a match's market with its recent price ticks per side, newest first. PRO — below that it returns ErrUpgradeRequired.
Only ListParams.Limit is honoured here; the endpoint takes no offset.
func (*Client) GetMatch ¶
GetMatch returns one match in full. FREE, with Match.Market embedded from PRO and Match.Analysis from ULTRA.
func (*Client) GetMatchAnalysis ¶
GetMatchAnalysis returns the model's analysis of a match. ULTRA — below that it returns ErrUpgradeRequired.
Both halves of the result may be nil for a match the model has not covered.
Example (TierWall) ¶
A 403 means the key is valid but the plan is too low. Telling that apart from a rejected key is the difference between "upgrade" and "check your credentials".
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
analysis, err := client.GetMatchAnalysis(context.Background(), 918273)
switch {
case errors.Is(err, livetennisapi.ErrUpgradeRequired):
var apiErr *livetennisapi.APIError
errors.As(err, &apiErr)
fmt.Printf("needs the %s tier\n", apiErr.RequiredTier)
return
case errors.Is(err, livetennisapi.ErrUnauthorized):
fmt.Println("the API key was rejected")
return
case errors.Is(err, livetennisapi.ErrNotFound):
fmt.Println("the model has not covered this match")
return
case err != nil:
log.Fatal(err)
}
if analysis.Thesis != nil {
fmt.Println(analysis.Thesis.Reasoning)
}
}
Output:
func (*Client) GetMatchRally ¶ added in v1.1.0
func (c *Client) GetMatchRally(ctx context.Context, matchID int64, params ListParams) (*RallyMatchDetail, error)
GetMatchRally returns rally construction addressed by the API's OWN match id, resolved through the optional link on RallyMatch.MatchID. ULTRA.
It answers ErrNotFound with code "not_charted" when the API holds the match but nobody charted it — deliberately distinct from "no such match", because most matches are not charted and a consumer walking the archive must tell the two apart.
func (*Client) GetMatchScore ¶
GetMatchScore returns just the current score, the lowest-latency read the REST API offers. FREE; ULTRA additionally populates Score.WinProbabilityP1 and Score.Danger.
It returns ErrNotFound for a match with no score yet, which is the normal answer for a fixture that has not started rather than a failure.
func (*Client) GetMatchStatistics ¶ added in v1.1.0
GetMatchStatistics returns in-play statistics for one match — aces, double faults, the serve split, hold/break percentages, break points, service and return points — in two families that are deliberately not merged (see StatisticsSide). ULTRA — below that it returns ErrUpgradeRequired.
A match the API holds nothing for answers 200 with coverage "none" and nil MatchStatistics.Players, not 404 — the match exists, and holding nothing for it is the honest answer. Statistics can be further behind the match than the score, which is why they carry their own as-of and are not on the Score object.
func (*Client) GetMatchTape ¶ added in v1.1.0
func (c *Client) GetMatchTape(ctx context.Context, matchID int64, params TapeParams) (*MatchTape, error)
GetMatchTape returns a match's point-by-point tape: the score sequence, per-point model probabilities where they exist, per-set tiebreak scores, and the coverage metadata that says how much of the match the tape holds. BASIC, or any History plan — below that it returns ErrUpgradeRequired.
It WORKS ON A LIVE MATCH, not only a completed one: the tape is assembled from whatever has been committed so far, including games played before you started watching. Client.GetMatchScore is one state; this is the sequence of states. Check TapeMeta.Coverage before backtesting — the tape is not guaranteed to cover the whole match.
func (*Client) GetRallyMatch ¶ added in v1.1.0
func (c *Client) GetRallyMatch(ctx context.Context, rallyMatchID int64, params ListParams) (*RallyMatchDetail, error)
GetRallyMatch returns one charted match with its points in play order, paged by params; RallyMatchDetail.Meta carries the match's full point count in Total. ULTRA — below that it returns ErrUpgradeRequired.
func (*Client) GetTournament ¶ added in v1.2.0
GetTournament returns one tournament by the stable id carried on match objects as Match.TournamentID. FREE.
func (*Client) GetUsage ¶ added in v1.2.0
GetUsage returns the calling key's own usage against its quota: tier, limits, today's calls current to the second, and a 30-day history. Any tier, and QUOTA-EXEMPT — polling it does not spend the budget it reports.
The per-minute window is not in the response; it rides on the X-RateLimit-* headers of every call (observe them with WithRateLimitObserver).
func (*Client) GetWSToken ¶ added in v1.1.0
GetWSToken mints a short-lived connection token for the high-fan-out push feed, returning the token, the WebSocket URL and the channel vocabulary — "match:{id}" per-match streams and "slate:all" for every live score frame. ULTRA — below that it returns ErrUpgradeRequired.
Mint a fresh token on every reconnect; the token expires after WSToken.ExpiresIn seconds.
func (*Client) Health ¶
Health reports whether the API is serving. It is the one endpoint that needs no API key, which makes it usable as a reachability check before you have credentials.
func (*Client) ListArchiveMatches ¶ added in v1.1.0
func (c *Client) ListArchiveMatches(ctx context.Context, params ArchiveMatchesParams) (*Page[ArchiveMatch], error)
ListArchiveMatches returns deep historical results, newest tournament first: ATP and WTA main draws, qualifying, challengers and futures, 1968 through 2022. BASIC, or any History plan — below that it returns ErrUpgradeRequired.
The archive is a separate id space from /matches — archive people are identified by name, not by roster player ids — and it ends where the API's own point-by-point coverage begins (2023-01), so no match is ever served from two datasets.
func (*Client) ListArchivePlayers ¶ added in v1.1.0
func (c *Client) ListArchivePlayers(ctx context.Context, params ArchivePlayersParams) (*Page[ArchivePlayerBio], error)
ListArchivePlayers returns archive player bios — hand, date of birth, country, height and career-high rank — ordered by name. BASIC, or any History plan.
These are the people of the results archive, in the archive's own id space: ArchivePlayerBio.ID is the corpus person id that archive match rows carry as ArchivePlayer.PlayerID, never a roster id.
func (*Client) ListCompletedMatches ¶
ListCompletedMatches returns finished matches, newest first, each with Match.Winner derived from the final sets and Match.Tape saying what point-by-point data exists for it. BASIC, or any History plan — below that it returns ErrUpgradeRequired.
This is the unfiltered shorthand; Client.ListHistoryMatches takes the full filter set (dates, coverage, tour, players, country).
func (*Client) ListFixtures ¶
func (c *Client) ListFixtures(ctx context.Context, params ListFixturesParams) (*Page[Fixture], error)
ListFixtures returns upcoming scheduled fixtures, earliest first. FREE.
Fixtures are primarily name-keyed: Fixture.Player1ID and Fixture.Player2ID are set only where a participant resolved to a roster record, so use Client.ListMatches with StatusUpcoming when you need full player objects.
func (*Client) ListHistoryMatches ¶ added in v1.1.0
func (c *Client) ListHistoryMatches(ctx context.Context, params HistoryMatchesParams) (*Page[Match], error)
ListHistoryMatches returns finished matches, newest first, each with Match.Winner derived from the final sets and Match.Tape saying what point-by-point data exists for it — so a whole page can be qualified in one call instead of one request per match. BASIC, or any History plan — below that it returns ErrUpgradeRequired.
func (*Client) ListHistoryPackages ¶ added in v1.1.0
func (c *Client) ListHistoryPackages(ctx context.Context, params HistoryPackagesParams) (*HistoryPackagesPage, error)
ListHistoryPackages returns the pre-built monthly bulk packages, newest period first. PRO, or a package subscription — the tape kind's floor; rally and rankings kinds and the year listing are gated as described on HistoryPackagesParams.
Coverage is not a contiguous run of months and is still being extended backwards, so treat this listing as the authoritative set of months that exist. The package files themselves stream from /history/packages/{period}?format=jsonl|csv.
func (*Client) ListMarkets ¶
ListMarkets returns the match-winner market or markets for a match. PRO — below that it returns ErrUpgradeRequired.
The markets carry no price ticks; use Client.GetMarketPrices for those.
func (*Client) ListMatchEvents ¶
func (c *Client) ListMatchEvents(ctx context.Context, matchID int64, params ListParams) (*Page[Event], error)
ListMatchEvents returns a match's events, newest first. PRO — below that it returns ErrUpgradeRequired.
func (*Client) ListMatchPrices ¶ added in v1.2.0
func (c *Client) ListMatchPrices(ctx context.Context, matchID int64, params MatchPricesParams) (*MatchPrices, error)
ListMatchPrices returns the bare recent price ticks of a match's mapped match-winner market, newest first — no market wrapper, which makes it the lightest polling read for prices. PRO — below that it returns ErrUpgradeRequired, and ErrNotFound when the match has no mapped market.
There is no offset: when MatchPricesMeta.HasMore reports the window was clipped, raise the limit or narrow the minutes window. Prices are prediction-market quotes in [0,1], not an official line, and can lag live scores; Price.Synthetic tags a quote estimated from mid rather than a live order book.
func (*Client) ListMatches ¶
ListMatches returns matches by lifecycle status, with each match's latest score. FREE.
A match that has not started carries a nil Match.Score:
page, err := client.ListMatches(ctx, livetennisapi.ListMatchesParams{
Status: livetennisapi.StatusUpcoming,
Tour: livetennisapi.TourWTA,
})
Example (ByTour) ¶
Restrict results to one circuit. Each tour covers its singles and doubles draws.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
page, err := client.ListMatches(context.Background(), livetennisapi.ListMatchesParams{
Status: livetennisapi.StatusLive,
Tour: livetennisapi.TourWTA,
})
if err != nil {
log.Fatal(err)
}
for _, match := range page.Data {
fmt.Println(match.Tournament, match.Score)
}
}
Output:
Example (RateLimited) ¶
Back off when the budget runs out rather than hammering the window.
package main
import (
"context"
"errors"
"fmt"
"os"
"time"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
_, err := client.ListMatches(context.Background(), livetennisapi.ListMatchesParams{})
var apiErr *livetennisapi.APIError
if errors.As(err, &apiErr) && errors.Is(err, livetennisapi.ErrRateLimited) {
wait := apiErr.RateLimit.RetryAfterOr(time.Minute)
fmt.Printf("rate limited, waiting %s (limit %d/min)\n", wait, apiErr.RateLimit.LimitOr(30))
time.Sleep(wait)
}
}
Output:
Example (Upcoming) ¶
An upcoming match has no score at all, so Match.Score is nil. The Score methods are nil-safe, but reading a field off it is not.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
page, err := client.ListMatches(context.Background(), livetennisapi.ListMatchesParams{
Status: livetennisapi.StatusUpcoming,
})
if err != nil {
log.Fatal(err)
}
for _, match := range page.Data {
if match.Score == nil {
fmt.Printf("%s starts at %s\n", match.Tournament, match.ScheduledTime)
continue
}
fmt.Printf("%s is under way: %s\n", match.Tournament, match.Score)
}
}
Output:
func (*Client) ListRallyMatches ¶ added in v1.1.0
func (c *Client) ListRallyMatches(ctx context.Context, params RallyMatchesParams) (*Page[RallyMatch], error)
ListRallyMatches returns charted matches with shot-by-shot data, newest first. ULTRA — below that it returns ErrUpgradeRequired.
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.
func (*Client) ListRankings ¶ added in v1.1.0
func (c *Client) ListRankings(ctx context.Context, params RankingsParams) (*RankingsPage, error)
ListRankings returns ranking records in force at a point in time. Every other ranking field in this API is the player's CURRENT value joined at read time — this endpoint is the point-in-time answer.
It has TWO modes with different tiers:
- Listing (PRO): leave Player empty and name exactly one System. Returns the full published table in rank order, the newest week at or before AsOf. Rows carry RankingRecord.PlayerName as published and a nil RankingRecord.PlayerID for players outside the roster, so the table has no silent holes.
- Per-player (ULTRA): set Player ids. Returns, per system, the newest record effective on or before AsOf for each player.
Below the mode's tier it returns ErrUpgradeRequired. Read RankingsMeta.Coverage before trusting an empty result — ITF and UTR history begins 2026-07-29 and cannot be reconstructed earlier.
func (*Client) ListTournaments ¶ added in v1.2.0
func (c *Client) ListTournaments(ctx context.Context, params TournamentsParams) (*Page[Tournament], error)
ListTournaments returns the tournament catalogue in name order — the id space Match.TournamentID joins. FREE.
Tier/category is populated only where the catalogues agree unambiguously (see Tournament.Category); it is never derived from the name.
func (*Client) ListWebhooks ¶ added in v1.2.0
ListWebhooks returns your registered webhooks with their delivery health — never the secret, which is shown only at registration. ULTRA, direct keys only.
func (*Client) SearchPlayers ¶
func (c *Client) SearchPlayers(ctx context.Context, params SearchPlayersParams) (*Page[Player], error)
SearchPlayers searches players by name, ranked players first. FREE.
The list carries no Player.Stats; use Client.GetPlayer for that.
type ConnectionError ¶
type ConnectionError struct {
// URL is the request URL that could not be reached.
URL string
// Timeout reports whether the failure was a deadline rather than a refusal.
Timeout bool
// Err is the underlying transport error.
Err error
}
ConnectionError means the request never produced a response.
It matches ErrConnection, and additionally ErrTimeout when the cause was a deadline. The underlying cause is available through errors.Unwrap, so context.Canceled and context.DeadlineExceeded stay detectable.
func (*ConnectionError) Is ¶
func (e *ConnectionError) Is(target error) bool
Is matches ErrConnection always, and ErrTimeout for a deadline.
func (*ConnectionError) Unwrap ¶
func (e *ConnectionError) Unwrap() error
Unwrap returns the underlying transport error.
type Coverage ¶ added in v1.1.0
type Coverage string
Coverage says how a match's point-by-point tape came to exist — and specifically whether the API watched the match.
It is a statement about how the rows were obtained, not a synonym for "complete": CoverageFromStart means every row was committed live from 0-0; CoverageReconstructedPartial means the tape is known not to cover the whole match and must not be backtested as if it did.
const ( // CoverageFromStart: watched live from 0-0; every row carries a real // timestamp. CoverageFromStart Coverage = "from_start" // CoveragePartial: watched, but recording began after play had started // and no reconstruction repairs the opening. CoveragePartial Coverage = "partial" // CoverageReconstructed: the tape opens with rows expanded after the fact // from a finished-match point record — true about the score, silent about // the clock. CoverageReconstructed Coverage = "reconstructed" // CoverageReconstructedPartial: reconstructed, and known not to cover the // whole match — it opens after 0-0 or stops short of the final score. CoverageReconstructedPartial Coverage = "reconstructed_partial" // CoverageNone: no rows at all. CoverageNone Coverage = "none" )
The coverage vocabulary. An unknown value passed as a filter is rejected with ErrBadRequest and code "bad_coverage", with the accepted values on APIError.AllowedValues.
type DataCompleteness ¶
type DataCompleteness struct {
// Known is how many of the considered fields are populated. nil for a
// doubles team, where the question does not apply.
Known *int `json:"known,omitempty"`
// Of is how many fields were considered. nil for a doubles team.
Of *int `json:"of,omitempty"`
// Missing names the unpopulated fields, for example ["backhand", "hand"].
// Empty when nothing is missing, and for a doubles team.
Missing []string `json:"missing,omitempty"`
// Note explains why completeness does not apply, when it does not. Set for
// a doubles team; empty for an individual.
Note string `json:"note,omitempty"`
}
DataCompleteness reports how much of a player's biography is populated.
It describes the biographical fields only — hand, backhand, birthday and the like — not the ranking or the identity, so 2 known of 5 still describes a perfectly usable player record.
It does not apply to a doubles team, which has no single biography. There the API sends Known and Of as null with an explanatory Note, so both are pointers: null means "not applicable", which is emphatically not zero. Check DataCompleteness.Applicable before reading them.
func (*DataCompleteness) Applicable ¶
func (d *DataCompleteness) Applicable() bool
Applicable reports whether per-player completeness is meaningful for this record. It is false for a doubles team, and for a nil receiver.
func (*DataCompleteness) Complete ¶
func (d *DataCompleteness) Complete() bool
Complete reports whether every considered biographical field is populated. It is false when completeness does not apply at all, so a doubles team is never reported complete; use DataCompleteness.Applicable to tell the two apart.
type Event ¶
type Event struct {
// Type is the kind of event.
Type EventType `json:"type,omitempty"`
// Player is which side it happened to, 1 or 2. nil when it applies to
// neither.
Player *int `json:"player,omitempty"`
// Timestamp is when it happened. Zero if absent.
Timestamp Time `json:"timestamp,omitzero"`
}
Event is something that happened in a match. PRO and above.
type Fixture ¶
type Fixture struct {
// ID is the fixture's identifier.
ID int64 `json:"id,omitempty"`
// EventDate is the calendar date of the fixture. Zero if absent.
EventDate Time `json:"event_date,omitzero"`
// StartTime is the scheduled start (UTC). Zero until the order of play
// assigns a time — a date-only fixture is a real state, not missing data.
StartTime Time `json:"start_time,omitzero"`
// Player1ID is player 1's roster id, when resolved. nil otherwise.
Player1ID *int64 `json:"player1_id,omitempty"`
// Player2ID is player 2's roster id, when resolved. nil otherwise.
Player2ID *int64 `json:"player2_id,omitempty"`
// Tour is the circuit, for example "wta" or "juniors_boys". A plain string
// rather than a [Tour] for the reason given on [Player.Tour]: the returned
// vocabulary is wider than the filter's.
Tour string `json:"tour,omitempty"`
// Tournament is the event name. Empty when unknown.
Tournament string `json:"tournament,omitempty"`
// Round is the round name. Empty when unknown.
Round string `json:"round,omitempty"`
// RoundCode is the normalised round, in the same vocabulary as
// [Match.RoundCode]. Empty when the label is unrecognised.
RoundCode string `json:"round_code,omitempty"`
// Surface is the court surface. Empty when unknown.
Surface string `json:"surface,omitempty"`
// Player1Name is player 1's name as printed on the calendar.
Player1Name string `json:"player1_name,omitempty"`
// Player2Name is player 2's name as printed on the calendar.
Player2Name string `json:"player2_name,omitempty"`
// Status is the fixture's status. Empty when unknown.
Status string `json:"status,omitempty"`
}
Fixture is a scheduled match on the calendar.
Fixtures are primarily name-keyed: Player1ID and Player2ID are set only when a participant resolves to a roster player record (an exact-key match, never a name guess), and names are always present regardless. Once a fixture goes live it appears through Client.ListMatches as a Match with full Player objects.
type H2HMeeting ¶ added in v1.1.0
type H2HMeeting struct {
// Era is "archive" or "current".
Era string `json:"era,omitempty"`
// Date is the meeting's date (for archive rows, the tournament start
// date). Zero when unknown.
Date Time `json:"date,omitzero"`
// Tournament is the event name. Empty when unknown.
Tournament string `json:"tournament,omitempty"`
// Level is the archive tier code. Empty on current rows.
Level string `json:"level,omitempty"`
// Round is the round label. Empty when unknown.
Round string `json:"round,omitempty"`
// RoundCode is the normalised round on current rows. Empty on archive
// rows.
RoundCode string `json:"round_code,omitempty"`
// Surface is the court surface. Empty when unknown.
Surface string `json:"surface,omitempty"`
// Score is the final score as published. Empty on current rows, which
// read their score from the match endpoints.
Score string `json:"score,omitempty"`
// Outcome says how the meeting ended ("completed", "retired",
// "walkover", ...), so walkovers and retirements can be excluded without
// losing them from the record. Empty when unknown.
Outcome string `json:"outcome,omitempty"`
// Winner is 1 or 2 of this head-to-head. nil when underivable.
Winner *int `json:"winner,omitempty"`
// ArchiveMatchID joins [Client.GetArchiveMatch] on archive rows. nil on
// current rows.
ArchiveMatchID *int64 `json:"archive_match_id,omitempty"`
// MatchID joins the match endpoints on current rows. nil on archive
// rows.
MatchID *int64 `json:"match_id,omitempty"`
}
H2HMeeting is one meeting in a head-to-head, newest first.
Era says which half of the product served the row: "archive" rows carry ArchiveMatchID, Level and Score; "current" rows carry MatchID and RoundCode and read their score from the match endpoints. Winner is 1 or 2 OF THIS HEAD-TO-HEAD — p1 and p2 as requested — not of the original match record.
type H2HSurfaceSplit ¶ added in v1.1.0
H2HSurfaceSplit is one surface's win split in a head-to-head.
type H2HTotals ¶ added in v1.1.0
type H2HTotals struct {
P1Wins int `json:"p1_wins"`
P2Wins int `json:"p2_wins"`
Meetings int `json:"meetings"`
// Undecided is meetings with no derivable winner.
Undecided int `json:"undecided"`
}
H2HTotals is the headline head-to-head record. Totals count meetings with a KNOWN winner; Undecided counts the rest and is never folded into the wins.
type HeadToHead ¶ added in v1.1.0
type HeadToHead struct {
// Players carries the resolved names as {"p1": {"name": ...}, "p2":
// {"name": ...}}. nil when no player matches the requested fragments —
// which also leaves Totals empty.
Players *struct {
P1 struct {
Name string `json:"name,omitempty"`
} `json:"p1,omitzero"`
P2 struct {
Name string `json:"name,omitempty"`
} `json:"p2,omitzero"`
} `json:"players,omitempty"`
// Totals is the headline record.
Totals H2HTotals `json:"totals,omitzero"`
// BySurface splits the wins by surface name, plus "unknown".
BySurface map[string]H2HSurfaceSplit `json:"by_surface,omitempty"`
// Meetings lists the meetings, newest first, capped at 200.
Meetings []H2HMeeting `json:"meetings,omitempty"`
// Stats is the per-player serve/return/break-point aggregate block over
// the pairing, present on ULTRA only. Carried as raw JSON because the v1
// schema does not pin its shape; unmarshal it yourself when you need it.
Stats json.RawMessage `json:"stats,omitempty"`
}
HeadToHead is the record between two players, assembled from BOTH halves of the product: the results archive (1968–2022), where the winner is a stored column, and the API's own completed matches (2023 onward), where the winner is derived from the final recorded state.
type Health ¶
type Health struct {
// Status is "ok" when the API is serving.
Status string `json:"status"`
// Version is the API version, "v1".
Version string `json:"version"`
}
Health is the response from Client.Health.
type HistoryMatchesParams ¶ added in v1.1.0
type HistoryMatchesParams struct {
// From and To bound the play date, as on [ListMatchesParams].
From string
To string
// Coverage keeps only matches whose tape has this coverage. Empty keeps
// everything. NOTE the filter is applied AFTER the page is cut, so a
// filtered page is routinely shorter than the limit (and may be empty)
// while later pages still hold matching matches — a short filtered page
// is not an end-of-data signal; read [ListMeta.HasMore], which
// [Paginate] does automatically.
Coverage Coverage
// Tour restricts results to one circuit. Empty means every tour.
Tour Tour
// Player restricts to matches involving any of these player ids, ≤50.
Player []int64
// Country restricts by either participant's country code, as on
// [ListMatchesParams].
Country string
// ListParams paginates the result.
ListParams
}
HistoryMatchesParams filters Client.ListHistoryMatches.
type HistoryPackage ¶ added in v1.1.0
type HistoryPackage struct {
// Period is the month, "YYYY-MM" — or the bare year "YYYY" on the
// yearly kinds ([PackageRally], [PackageArchive]).
Period string `json:"period,omitempty"`
// Status is "ready" — only built months are listed or served.
Status string `json:"status,omitempty"`
// MatchCount is the matches in the package — or, on a rankings package,
// the players covered. nil when unknown.
MatchCount *int `json:"match_count,omitempty"`
// RowCount is the tape rows — or ranking records — in the package. nil
// when unknown.
RowCount *int `json:"row_count,omitempty"`
// Files lists the downloadable files.
Files []PackageFile `json:"files,omitempty"`
// BuiltAt is when the package was built. Zero if absent.
BuiltAt Time `json:"built_at,omitzero"`
// Kind is present only on non-tape packages, so the shape a tape client
// already parses is unchanged. Empty means tape.
Kind PackageKind `json:"kind,omitempty"`
}
HistoryPackage is a published monthly bulk package. Coverage is not a contiguous run of months and is still being extended backwards, so treat the listing as the authoritative set of months that exist.
type HistoryPackagesPage ¶ added in v1.1.0
type HistoryPackagesPage struct {
// Data holds the packages, newest period first.
Data []HistoryPackage `json:"data"`
// Meta carries the count, and echoes Year when one was asked for.
Meta struct {
Count int `json:"count,omitempty"`
Year string `json:"year,omitempty"`
} `json:"meta,omitzero"`
}
HistoryPackagesPage is the response from Client.ListHistoryPackages.
type HistoryPackagesParams ¶ added in v1.1.0
type HistoryPackagesParams struct {
// Kind is the package family: [PackageTape] (the API's default),
// [PackageRally], [PackageRankings] or [PackageArchive]. Rally and
// rankings need ULTRA; archive rides the tape entitlement.
Kind PackageKind
// Year asks for the year-archive listing — every published month of
// "YYYY". That needs core ULTRA, History Business, or a one-year
// package.
Year string
}
HistoryPackagesParams filters Client.ListHistoryPackages.
type ListFixturesParams ¶
type ListFixturesParams struct {
// Tour restricts results to one circuit. Empty means every tour.
Tour Tour
// ListParams paginates the result.
ListParams
}
ListFixturesParams filters Client.ListFixtures.
type ListMatchesParams ¶
type ListMatchesParams struct {
// Status selects the lifecycle stage: [StatusLive], [StatusUpcoming] or
// [StatusCompleted]. Empty means the API's default, which is live.
Status MatchStatus
// Tour restricts results to one circuit, its singles and doubles draws
// alike. Empty means every tour. An unrecognised value is rejected with
// [ErrBadRequest] rather than quietly ignored.
Tour Tour
// Player restricts results to matches where any listed player id is
// EITHER participant, up to 50 ids; multiple ids return the deduplicated
// union. An unknown id yields an empty list, not an error.
Player []int64
// Country restricts results to matches where either participant's
// country equals this lowercase 3-letter code — the same IOC-style
// vocabulary [Player.Country] returns (e.g. "ned", "sui"), NOT ISO-3166.
// Players with no recorded country never match.
Country string
// From and To bound the play date: "YYYY-MM-DD" or an ISO-8601 UTC
// datetime. A bare date is a UTC day boundary, and To includes the whole
// day. An unparseable value is rejected with [ErrBadRequest] and code
// "bad_date"; To must not precede From.
From string
To string
// ListParams paginates the result.
ListParams
}
ListMatchesParams filters Client.ListMatches.
type ListMeta ¶
type ListMeta struct {
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
Count int `json:"count,omitempty"`
// Total is the size of the whole filtered set. nil when the API cannot
// count it cheaply — completed-match listings return null here — so nil
// means "unknown", never "zero results".
Total *int `json:"total,omitempty"`
// HasMore reports whether results exist beyond this page. nil when the
// endpoint did not say, in which case the only end-of-data signal is a
// short page. [Paginate] prefers this field when it is present, which
// matters on the coverage-filtered history listing, where a filtered page
// is routinely shorter than the limit while later pages still hold
// matches.
HasMore *bool `json:"has_more,omitempty"`
// MatchID echoes the filter on [Client.ListMarkets], which is the only
// endpoint that sets it. Zero elsewhere.
MatchID int64 `json:"match_id,omitempty"`
}
ListMeta is the pagination envelope returned beside a list response.
Count describes the page just returned, not the size of the whole collection, so it cannot be used to compute a page count. Detect the end of a list by receiving fewer items than you asked for — which is what Paginate does.
type ListParams ¶
type ListParams struct {
// Limit is how many items to return, 1 to 200.
Limit int
// Offset is how many items to skip.
Offset int
}
ListParams is the pagination shared by every list endpoint.
A zero field is omitted and the API's own default applies: 50 items from offset 0. Limit is capped at MaxLimit.
type Market ¶
type Market struct {
// ID is the market's identifier.
ID int64 `json:"id,omitempty"`
// Question is the market's question text. Empty when unset.
Question string `json:"question,omitempty"`
// Status is "active", "resolved" or "closed". Empty when unknown.
Status string `json:"status,omitempty"`
// Volume is traded volume. nil when absent, which is not zero volume.
Volume *float64 `json:"volume,omitempty"`
// Liquidity is available liquidity. nil when absent.
Liquidity *float64 `json:"liquidity,omitempty"`
// EndDate is when the market closes. Zero if absent.
EndDate Time `json:"end_date,omitzero"`
// Prices holds recent ticks, newest first. Populated by
// [Client.GetMarketPrices] and by the match-detail embed; empty on
// [Client.ListMarkets].
Prices []Price `json:"prices,omitempty"`
}
Market is a match-winner market. PRO and above.
type Match ¶
type Match struct {
// ID is the match's stable identifier.
ID int64 `json:"id,omitempty"`
// Tournament is the event name.
Tournament string `json:"tournament,omitempty"`
// Tour is the match's circuit, in the SAME vocabulary the tour filter
// accepts — unlike [Player.Tour] and [Fixture.Tour], this one is safe to
// compare against the [Tour] constants, and a match selected by a tour
// filter always carries that value here. Empty when the feed never stated
// a tour or the event has no public tour name (exhibitions, team and
// mixed events).
Tour Tour `json:"tour,omitempty"`
// TournamentID is the stable tournament identity: one id per tournament
// and event type, stable across seasons. Empty on matches ingested before
// the catalogue covered their tournament.
TournamentID string `json:"tournament_id,omitempty"`
// Surface is "hard", "clay" or "grass". Empty when unknown.
Surface string `json:"surface,omitempty"`
// Indoor reports whether the court is indoors.
Indoor bool `json:"indoor,omitempty"`
// Format is "BO3" or "BO5". Empty when unknown.
Format string `json:"format,omitempty"`
// Round is the round name, such as "QF". Empty when unknown.
Round string `json:"round,omitempty"`
// RoundCode is the round in the archive's controlled vocabulary — "F",
// "SF", "QF", "R16" through "R128", "RR", "BR", "Q" (a qualifying round
// the feed did not number), "Q1" to "Q4", "ER" — normalised from the
// free-text Round above. Empty when the label is unrecognised, never
// guessed.
RoundCode string `json:"round_code,omitempty"`
// Status is the lifecycle state: live, upcoming, completed or cancelled.
Status MatchStatus `json:"status,omitempty"`
// EventStatus is the API's finer-grained status string, such as a
// suspension reason. Empty when unset.
EventStatus string `json:"event_status,omitempty"`
// IsDoubles reports whether this is a doubles match.
IsDoubles bool `json:"is_doubles,omitempty"`
// ScheduledTime is the scheduled start. Zero when not yet scheduled.
ScheduledTime Time `json:"scheduled_time,omitzero"`
// Players holds both sides.
Players Players `json:"players,omitzero"`
// Score is the latest score, and is nil for an upcoming match that has
// not started. Always check it before dereferencing; the [Score] methods
// are nil-safe for exactly this reason.
Score *Score `json:"score,omitempty"`
// Winner is 1 or 2 on a completed match, derived from the final sets.
// nil while the match is unfinished or the result is indeterminate.
Winner *int `json:"winner,omitempty"`
// Withdrew is which player retired or conceded the walkover, 1 or 2.
// Present only on a completed match whose EventStatus is "Retired" or
// "Walk Over" and whose winner is derivable — the withdrawer is the loser
// by the rules of the sport. nil everywhere else.
Withdrew *int `json:"withdrew,omitempty"`
// Tape says what point-by-point data the API holds for this match.
// Populated by [Client.ListHistoryMatches] and [Client.ListCompletedMatches]
// only; nil everywhere else.
Tape *TapeInfo `json:"tape,omitempty"`
// Market is the match-winner market, embedded by [Client.GetMatch] for
// PRO and above. nil otherwise.
Market *Market `json:"market,omitempty"`
// Analysis is the model analysis, embedded by [Client.GetMatch] for
// ULTRA. nil otherwise.
Analysis *Analysis `json:"analysis,omitempty"`
}
Match is a tennis match.
Market is present from PRO and Analysis from ULTRA. Below those tiers the API omits them entirely, so nil means "not entitled or not available" and never "no market exists".
type MatchPrices ¶ added in v1.2.0
type MatchPrices struct {
// Data holds the ticks, newest first.
Data []Price `json:"data"`
// Meta is the window envelope.
Meta MatchPricesMeta `json:"meta,omitzero"`
}
MatchPrices is the bare price ticks of a match's mapped match-winner market, newest first, from Client.ListMatchPrices — no market wrapper.
type MatchPricesMeta ¶ added in v1.2.0
type MatchPricesMeta struct {
// MatchID echoes the match asked for.
MatchID int64 `json:"match_id,omitempty"`
// Count is how many ticks this response holds.
Count int `json:"count,omitempty"`
// HasMore reports that the window was clipped at the limit — older ticks
// exist. There is no offset on this endpoint; raise the limit or narrow
// the minutes window instead.
HasMore bool `json:"has_more,omitempty"`
// Limit echoes the applied limit.
Limit int `json:"limit,omitempty"`
// Minutes echoes the lookback window. nil when none was asked for.
Minutes *int `json:"minutes,omitempty"`
}
MatchPricesMeta is the envelope beside a bare price-tick response.
type MatchPricesParams ¶ added in v1.2.0
type MatchPricesParams struct {
// Limit is how many ticks to return, 1 to [MaxPriceTicks]. Zero means
// the API's default of 100. Values above the cap are clamped.
Limit int
// Minutes bounds the lookback window, 1 to 1440. Zero means no window.
Minutes int
}
MatchPricesParams bounds Client.ListMatchPrices.
type MatchStatistics ¶ added in v1.1.0
type MatchStatistics struct {
// MatchID is the match these statistics describe.
MatchID int64 `json:"match_id,omitempty"`
// Coverage summarises the response: "live", "final", "stale", "none" or
// "diverged". Branch on Freshness for the per-family truth.
Coverage string `json:"coverage,omitempty"`
// AsOf is when the underlying record was last updated. Zero if absent.
AsOf Time `json:"as_of,omitzero"`
// AgeSeconds is measured behind the newest SCORE row, not the wall
// clock. nil when unknown.
AgeSeconds *int `json:"age_seconds,omitempty"`
// GamesCounted is how many games the derived family is built from.
GamesCounted int `json:"games_counted,omitempty"`
// TiebreakGamesExcluded counts tiebreaks left out of the derived family:
// the live record collapses a whole tiebreak onto one entry, so most of
// its points are lost.
TiebreakGamesExcluded int `json:"tiebreak_games_excluded,omitempty"`
// InconsistentGamesExcluded counts games whose recorded outcome is
// neither a legal hold nor a legal break.
InconsistentGamesExcluded int `json:"inconsistent_games_excluded,omitempty"`
// SetsCovered names the sets the derived family covers.
SetsCovered []int `json:"sets_covered,omitempty"`
// Freshness is the per-family coverage and age.
Freshness StatisticsFreshness `json:"freshness,omitzero"`
// Detail explains a coverage of "none". Empty otherwise.
Detail string `json:"detail,omitempty"`
// Players holds both sides. nil when coverage is "none".
Players *struct {
P1 *StatisticsSide `json:"p1,omitempty"`
P2 *StatisticsSide `json:"p2,omitempty"`
} `json:"players,omitempty"`
}
MatchStatistics is the in-play statistics for one match. ULTRA.
A coverage of "none" on both families returns a 200 with nil Players, not a 404 — the match exists, and holding nothing for it is the honest answer.
type MatchStatus ¶
type MatchStatus string
MatchStatus is a match's lifecycle state.
const ( StatusLive MatchStatus = "live" StatusUpcoming MatchStatus = "upcoming" StatusCompleted MatchStatus = "completed" StatusCancelled MatchStatus = "cancelled" )
The lifecycle states a match moves through. Only the first three are accepted as a filter by Client.ListMatches; "cancelled" appears in payloads but is not a query value.
type MatchTape ¶ added in v1.1.0
type MatchTape struct {
// Match is the match header.
Match Match `json:"match"`
// Tape is the score sequence, chronological. Empty when the match was
// neither watched nor reconstructable.
Tape []TapeRow `json:"tape,omitempty"`
// Tiebreaks holds per-set tiebreak final scores from observed states
// only, aligned to the sets of the final scoreline: an entry for a 7-6
// set whose observed maximum tiebreak state is a valid terminal shape,
// nil per set otherwise — a breaker whose closing point the feed skipped
// reads nil rather than an under-report. nil entirely when the match has
// no 7-6 set.
Tiebreaks []*SetTiebreak `json:"tiebreaks,omitempty"`
// Profiles holds the model's profiles for the match, oldest first, in the
// same shape as [Analysis].Profile.
Profiles []Profile `json:"profiles,omitempty"`
// Meta is the tape's coverage metadata.
Meta TapeMeta `json:"meta,omitzero"`
}
MatchTape is the response from Client.GetMatchTape: a match header, its chronological score sequence, and the coverage metadata that says how much of the match the tape actually holds. Check Meta.Coverage before backtesting.
type MeasuredDivergence ¶ added in v1.1.0
type MeasuredDivergence struct {
Reason string `json:"reason,omitempty"`
GamesInStatistics int `json:"games_in_statistics,omitempty"`
GamesInScore int `json:"games_in_score,omitempty"`
// DeltaGames is positive when the statistics are ahead of the score,
// which staleness cannot cause.
DeltaGames int `json:"delta_games,omitempty"`
Detail string `json:"detail,omitempty"`
}
MeasuredDivergence says why the measured values were withheld, with both match states.
type Option ¶
type Option func(*Client)
Option configures a Client. Options are applied in order by New.
func WithAuthMethod ¶
func WithAuthMethod(m AuthMethod) Option
WithAuthMethod selects which header carries the API key. The default is AuthBearer; pass AuthAPIKey for the X-API-Key header.
func WithBaseURL ¶
WithBaseURL overrides the API root, for a proxy or a test server. A trailing slash is trimmed. An empty value is ignored.
func WithHTTPClient ¶
WithHTTPClient sets the HTTP client used for every request.
This replaces the default client outright, including its 30-second timeout, so set a Timeout on the client you pass unless your transport imposes one or you rely on per-call context deadlines.
func WithMaxRetries ¶
WithMaxRetries sets how many times a retryable failure is retried, on top of the initial attempt. Zero disables retrying. Negative values are clamped to zero.
Only 429 and 5xx are retried, plus transport failures. Every other 4xx is a client-side mistake — a bad key, an unentitled tier, a missing id — that cannot start working, and retrying it only burns rate-limit budget.
func WithRateLimitObserver ¶
WithRateLimitObserver registers a callback invoked with the rate-limit budget reported on every response, successful or not. It is the only way to read the budget on a successful call, since a successful call returns no error to carry it.
The callback runs synchronously on the calling goroutine before the method returns, and is called once per HTTP attempt — so a retried request calls it more than once. Keep it fast and make it safe for concurrent use.
Example ¶
Watch the remaining budget on calls that succeed, where there is no error to carry it.
package main
import (
"context"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(
os.Getenv("LIVETENNISAPI_KEY"),
livetennisapi.WithRateLimitObserver(func(rl livetennisapi.RateLimit) {
if rl.RemainingOr(-1) >= 0 && rl.RemainingOr(0) < 5 {
log.Printf("only %d requests left until %s", rl.RemainingOr(0), rl.Reset)
}
}),
)
if _, err := client.Health(context.Background()); err != nil {
log.Fatal(err)
}
}
Output:
func WithUserAgent ¶
WithUserAgent overrides the User-Agent header.
Please keep a token identifying your application; it is how API support can tell your traffic apart when you ask about it.
type PackageFile ¶ added in v1.1.0
type PackageFile struct {
// Format is "jsonl" or "csv". The JSONL file holds one line per match (a
// whole tape object per line, coverage meta included), not one line per
// point; the CSV is flattened to one row per point and carries no
// coverage columns.
Format string `json:"format,omitempty"`
Filename string `json:"filename,omitempty"`
Bytes int64 `json:"bytes,omitempty"`
SHA256 string `json:"sha256,omitempty"`
}
PackageFile is one downloadable file of a bulk package.
type PackageKind ¶ added in v1.1.0
type PackageKind string
PackageKind selects a bulk-package family on Client.ListHistoryPackages.
const ( // PackageTape is the point-by-point match tapes. PRO and above, or a // package subscription. PackageTape PackageKind = "tape" // PackageRally is the rally-construction packages. ULTRA. PackageRally PackageKind = "rally" // PackageRankings is the as-of ranking records. ULTRA. PackageRankings PackageKind = "rankings" // PackageArchive is the results archive (1968–2022) as yearly exports — // the period is the bare year "YYYY". Same entitlement as the tape // packages, not ULTRA. PackageArchive PackageKind = "archive" )
The package families. The default is tape, so a tape-only client never sees a new kind of row appear.
type Page ¶
type Page[T any] struct { // Data holds the items for this page. Never nil after a successful call; // an empty page decodes to an empty slice. Data []T `json:"data"` // Meta is the pagination envelope. Meta ListMeta `json:"meta,omitzero"` }
Page is one page of a list endpoint: the API's {data, meta} envelope.
type Player ¶
type Player struct {
// ID is the player's stable identifier.
ID int64 `json:"id,omitempty"`
// Name is the player's display name.
Name string `json:"name,omitempty"`
// Tour is the circuit this player is recorded on: "atp", "wta",
// "challenger", "itf", "juniors_boys" or "juniors_girls". Empty when
// unknown.
//
// This is a plain string, not a [Tour], because the values the API returns
// here are not the values it accepts as a filter — the juniors draws split
// into two here but are selected with the single filter [TourJuniors].
Tour string `json:"tour,omitempty"`
// Country is the country code. Empty when unknown.
Country string `json:"country,omitempty"`
// Ranking is the current world ranking. nil when the player is unranked,
// which is not the same as being ranked 0.
Ranking *int `json:"ranking,omitempty"`
// RankingPoints is the current ranking points total. nil when unknown.
RankingPoints *int `json:"ranking_points,omitempty"`
// RankingMovement is "up", "down" or "same". Empty when unknown.
RankingMovement string `json:"ranking_movement,omitempty"`
// Hand is the playing hand, "R" or "L". Empty when unknown.
Hand string `json:"hand,omitempty"`
// Backhand is 1 for one-handed, 2 for two-handed. nil when unknown.
Backhand *int `json:"backhand,omitempty"`
// Birthday is the date of birth, a calendar date with no time. Zero when
// unknown.
Birthday Time `json:"birthday,omitzero"`
// IsDoublesTeam reports whether this entry is a doubles pairing rather
// than an individual.
IsDoublesTeam bool `json:"is_doubles_team,omitempty"`
// DataCompleteness says how much biographical detail is known for this
// player, so you can tell "not in the feed" from "not yet fetched" without
// probing. Present on every player in a match payload; lower tours carry
// far less detail than the main tour.
DataCompleteness *DataCompleteness `json:"data_completeness,omitempty"`
// Stats is populated by [Client.GetPlayer] only, never by the search
// endpoint. nil elsewhere.
Stats *PlayerStats `json:"stats,omitempty"`
}
Player is a player's identity, ranking and, on the single-player endpoint, cached statistics.
A doubles entry is modelled as a single Player with IsDoublesTeam set and both names in Name.
Example (DataCompleteness) ¶
DataCompleteness says how much biography the feed holds for a player, so an empty field can be read as "not in the feed" rather than "not fetched".
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
client := livetennisapi.New(os.Getenv("LIVETENNISAPI_KEY"))
player, err := client.GetPlayer(context.Background(), 2317)
if err != nil {
log.Fatal(err)
}
dc := player.DataCompleteness
switch {
case dc == nil || !dc.Applicable():
// A doubles team has no single biography to be complete.
fmt.Println(player.Name, "— per-player completeness does not apply")
case dc.Complete():
fmt.Println(player.Name, "— full biography")
default:
fmt.Printf("%s: %d of %d fields known, missing %v\n",
player.Name, *dc.Known, *dc.Of, dc.Missing)
}
}
Output:
type PlayerStats ¶
type PlayerStats struct {
// Ratings is the ratings object, or nil.
Ratings json.RawMessage `json:"ratings,omitempty"`
// Season is the season-by-season array, or nil.
Season json.RawMessage `json:"season,omitempty"`
}
PlayerStats is the cached statistics block on a single player.
Both halves are carried as raw JSON: the API does not pin their shape in the v1 schema, so decoding them into fixed structs here would break the moment the model behind them changes. Unmarshal them yourself when you need them.
type Players ¶
type Players struct {
// P1 is player 1, the side that "1" refers to everywhere else: in
// [Score.Server], [Match.Winner], [Price.Side] and [Event.Player].
P1 *Player `json:"p1,omitempty"`
// P2 is player 2.
P2 *Player `json:"p2,omitempty"`
}
Players is the pair of players in a match.
type Price ¶
type Price struct {
// Side is 1 for player 1's outcome, 2 for player 2's. nil when unknown.
Side *int `json:"side,omitempty"`
// Bid is the best bid, from 0 to 1. nil when absent — which is different
// from a bid of 0, meaning no one will buy at any price.
Bid *float64 `json:"bid,omitempty"`
// Ask is the best ask, from 0 to 1. nil when absent.
Ask *float64 `json:"ask,omitempty"`
// Mid is the midpoint between bid and ask. nil when absent.
Mid *float64 `json:"mid,omitempty"`
// Spread is the gap between bid and ask. nil when absent.
Spread *float64 `json:"spread,omitempty"`
// PriceSource is the feed category, for example "prediction_market".
// Empty when unstated.
PriceSource string `json:"price_source,omitempty"`
// Synthetic reports whether the bid/ask were estimated from mid rather
// than read from a live order book: true = estimated, false = real
// top-of-book, nil = unknown (older ticks) — tagged so a synthesised
// quote is never mistaken for a live book.
Synthetic *bool `json:"synthetic,omitempty"`
// Timestamp is when the tick was observed. Zero if absent.
Timestamp Time `json:"timestamp,omitzero"`
}
Price is one price tick on a match-winner market.
type Profile ¶
type Profile struct {
// WinProbabilityP1 is the probability player 1 wins, from 0 to 1. nil
// when unset.
WinProbabilityP1 *float64 `json:"win_probability_p1,omitempty"`
// ExpectedCloseness is how close the model expects the match to be. nil
// when unset.
ExpectedCloseness *float64 `json:"expected_closeness,omitempty"`
// VolatilityRating is "low", "med" or "high". Empty when unset.
VolatilityRating string `json:"volatility_rating,omitempty"`
// KeyFactors are the drivers behind the profile.
KeyFactors []string `json:"key_factors,omitempty"`
// CreatedAt is when the profile was generated. Zero if absent.
CreatedAt Time `json:"created_at,omitzero"`
}
Profile is the model's pre-match shape of a contest.
type RallyMatch ¶ added in v1.1.0
type RallyMatch struct {
// RallyMatchID is the id this product is keyed on, usable with
// [Client.GetRallyMatch].
RallyMatchID int64 `json:"rally_match_id,omitempty"`
// SourceID is the stable corpus key.
SourceID string `json:"source_id,omitempty"`
// MatchID is the API's own match id, when the charted match is also one
// it holds. nil otherwise — most charted matches predate the API's own
// collection.
MatchID *int64 `json:"match_id,omitempty"`
// Date is the match date. Zero when unknown.
Date Time `json:"date,omitzero"`
// Tournament is the event name. Empty when unknown.
Tournament string `json:"tournament,omitempty"`
// Round is the round label. Empty when unknown.
Round string `json:"round,omitempty"`
// Surface is the court surface. Empty when unknown.
Surface string `json:"surface,omitempty"`
// Gender is "M" or "W". Empty when unknown.
Gender string `json:"gender,omitempty"`
// BestOf is 3 or 5. nil when unrecorded.
BestOf *int `json:"best_of,omitempty"`
// Players holds both players, in order.
Players []RallyPlayerRef `json:"players,omitempty"`
// Points is how many points were charted in this match.
Points int `json:"points,omitempty"`
// PointsParsed is how many of them the parser read cleanly — the
// per-match quality number.
PointsParsed int `json:"points_parsed,omitempty"`
}
RallyMatch is one charted match with shot-by-shot data. ULTRA.
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 reaches back decades and concentrates on the biggest events, so keying it on the API's own match ids would hide most of it.
type RallyMatchDetail ¶ added in v1.1.0
type RallyMatchDetail struct {
RallyMatch
// Rally holds the charted points for this page.
Rally []RallyPoint `json:"rally,omitempty"`
// Meta paginates the points; Total is the match's full point count.
Meta ListMeta `json:"meta,omitzero"`
}
RallyMatchDetail is one charted match with its points, in play order.
type RallyMatchesParams ¶ added in v1.1.0
type RallyMatchesParams struct {
// Player is a substring match on either player name.
Player string
// From and To bound the match date, "YYYY-MM-DD".
From string
To string
// Surface filters by court surface.
Surface string
// Gender is "M" or "W". Empty means both.
Gender string
// ListParams paginates the result.
ListParams
}
RallyMatchesParams filters Client.ListRallyMatches.
type RallyPlayerRef ¶ added in v1.1.0
type RallyPlayerRef struct {
// Name is the player's name. Empty when unknown.
Name string `json:"name,omitempty"`
// Hand is "R", "L", "U" (unknown) or "A" (ambidextrous). Empty when
// unrecorded.
Hand string `json:"hand,omitempty"`
}
RallyPlayerRef is one player of a charted match. Charted people are identified by name — the charted corpus is its own population, not the roster.
type RallyPoint ¶ added in v1.1.0
type RallyPoint struct {
// Point is the point's ordinal within the match.
Point int `json:"point,omitempty"`
// Set is [sets_p1, sets_p2] at this point; either element may be nil
// where the charter's score bookkeeping had a hole.
Set []*int `json:"set,omitempty"`
// Games is [games_p1, games_p2] at this point, with the same caveat.
Games []*int `json:"games,omitempty"`
// Score is the point score as charted, e.g. "30-40". Empty when unknown.
Score string `json:"score,omitempty"`
// Game is the game number. nil when unknown.
Game *int `json:"game,omitempty"`
// IsTiebreak reports whether the point was played in a tiebreak.
IsTiebreak bool `json:"is_tiebreak,omitempty"`
// Server is who served, 1 or 2. nil when unknown.
Server *int `json:"server,omitempty"`
// PointWinner is who won the point, 1 or 2. nil when unknown.
PointWinner *int `json:"point_winner,omitempty"`
// Raw is the charter's shot string, both serves joined by ";" when the
// first was a fault.
Raw string `json:"raw,omitempty"`
// Parsed reports whether the notation was read cleanly.
Parsed bool `json:"parsed,omitempty"`
// ServeNumber is 1 or 2. nil when unknown.
ServeNumber *int `json:"serve_number,omitempty"`
// ServeDirection is "wide", "body" or "down_the_t". Empty when unknown.
ServeDirection string `json:"serve_direction,omitempty"`
// RallyLength is strokes including the serve: an ace is 1, a double
// fault 0. nil when unknown.
RallyLength *int `json:"rally_length,omitempty"`
// Outcome is "winner", "forced_error", "unforced_error", "error" (the
// charter recorded a miss without saying whether it was forced — never
// guessed) or "other". Empty when unknown.
Outcome string `json:"outcome,omitempty"`
// ErrorLocation is "net", "wide", "deep" or "wide_and_deep". Empty when
// the point did not end in an error, or the location went unrecorded.
ErrorLocation string `json:"error_location,omitempty"`
// EndingStroke and EndingWing describe the shot that ended the point.
// Empty when unknown.
EndingStroke string `json:"ending_stroke,omitempty"`
EndingWing string `json:"ending_wing,omitempty"`
IsAce bool `json:"is_ace,omitempty"`
IsDoubleFault bool `json:"is_double_fault,omitempty"`
IsServeAndVolley bool `json:"is_serve_and_volley,omitempty"`
// Shots holds the strokes of the rally, in order.
Shots []RallyShot `json:"shots,omitempty"`
}
RallyPoint is one charted point.
Raw is the charter's own string, verbatim, and is always present; the parsed fields are the API's reading of it. Parsed is false when the notation contained something that could not be read cleanly — the recognised part is still returned, and a consumer who wants only unambiguous rows filters on Parsed.
type RallyShot ¶ added in v1.1.0
type RallyShot struct {
// Number is the stroke's position in the rally.
Number int `json:"number,omitempty"`
// Code is the charter's raw code, e.g. "f".
Code string `json:"code,omitempty"`
// Stroke is the parsed stroke type: "serve", "groundstroke", "slice",
// "volley", "half_volley", "swinging_volley", "overhead", "drop_shot",
// "lob", "trick" or "unknown". Empty when unparsed.
Stroke string `json:"stroke,omitempty"`
// Wing is the side the ball was struck FROM, "forehand" or "backhand".
// Empty when unknown.
Wing string `json:"wing,omitempty"`
// Direction is where the ball was sent: "forehand_side", "middle" or
// "backhand_side". Empty when unknown.
Direction string `json:"direction,omitempty"`
// Depth is "shallow", "mid" or "deep". Empty when unknown.
Depth string `json:"depth,omitempty"`
// Position is "approaching", "at_net" or "baseline". Empty when unknown.
Position string `json:"position,omitempty"`
}
RallyShot is one stroke of a charted point. Shots are numbered from the serve: serve 1, return 2, the server's next ball 3.
type RankingRecord ¶ added in v1.1.0
type RankingRecord struct {
// PlayerID is the roster player id. nil on listing rows for players
// outside the roster — the published table is served without silent
// holes, so those rows keep their PlayerName.
PlayerID *int64 `json:"player_id,omitempty"`
// PlayerName is the name as the ranking publisher printed it. Present on
// listing rows; empty on per-player records.
PlayerName string `json:"player_name,omitempty"`
// System is the ranking system this record belongs to.
System RankingSystem `json:"system,omitempty"`
// Tour is the circuit, where the system implies one. Empty otherwise.
Tour string `json:"tour,omitempty"`
// Rank is the published rank. nil for UTR, which has no rank.
Rank *int `json:"rank,omitempty"`
// Points is the published points total. nil for UTR.
Points *int `json:"points,omitempty"`
// PreviousRank is the rank at the immediately preceding snapshot week.
// ATP and WTA only; nil when no prior week is held, and always nil for
// ITF and UTR.
PreviousRank *int `json:"previous_rank,omitempty"`
// RankMovement is the circuit's own signed weekly movement. ITF systems
// only; nil elsewhere.
RankMovement *int `json:"rank_movement,omitempty"`
// Rating is the UTR rating. nil for every other system.
Rating *float64 `json:"rating,omitempty"`
// EffectiveDate is the publication week this record took effect. For
// records ingested live rather than from the official weekly publication
// it is bucketed to the observed week, so it can sit up to six days later
// than the moment the value took effect. Zero if absent.
EffectiveDate Time `json:"effective_date,omitzero"`
// ObservedAt is when the record was observed. Zero if absent.
ObservedAt Time `json:"observed_at,omitzero"`
}
RankingRecord is one ranking record in force at the requested instant.
type RankingSystem ¶ added in v1.1.0
type RankingSystem string
RankingSystem is a ranking system accepted by Client.ListRankings.
Systems are never collapsed into a single "rank" — they are not comparable. ATP/WTA and the ITF circuits carry rank and points; UTR carries a rating with nil rank and points, because it is a rating and has neither.
const ( RankingATP RankingSystem = "atp" RankingWTA RankingSystem = "wta" RankingITFJuniors RankingSystem = "itf_jt" RankingITFMen RankingSystem = "itf_mt" RankingITFWomen RankingSystem = "itf_wt" RankingUTR RankingSystem = "utr" )
The ranking systems.
type RankingsCoverage ¶ added in v1.1.0
type RankingsCoverage struct {
// AsOf echoes the requested as-of date. Zero when the latest was asked
// for.
AsOf Time `json:"as_of,omitzero"`
// PlayersRequested and PlayersResolved count the per-player mode's ids.
PlayersRequested int `json:"players_requested,omitempty"`
PlayersResolved int `json:"players_resolved,omitempty"`
// SystemsRequested and SystemsResolved name the systems asked for and
// answered.
SystemsRequested []string `json:"systems_requested,omitempty"`
SystemsResolved []string `json:"systems_resolved,omitempty"`
// OldestAvailable is the earliest effective date held, per requested
// system. A zero time means the system holds nothing.
OldestAvailable map[string]Time `json:"oldest_available,omitempty"`
}
RankingsCoverage says what resolved against what was asked. Read it before trusting an empty result: ITF and UTR history begins 2026-07-29 and cannot be reconstructed earlier, so an as-of before that date correctly returns nothing for those systems.
type RankingsMeta ¶ added in v1.1.0
type RankingsMeta struct {
ListMeta
// Coverage says what resolved against what was asked.
Coverage RankingsCoverage `json:"coverage,omitzero"`
}
RankingsMeta is the pagination envelope of a rankings response, extended with coverage.
type RankingsPage ¶ added in v1.1.0
type RankingsPage struct {
// Data holds the ranking records.
Data []RankingRecord `json:"data"`
// Meta is the pagination envelope with coverage.
Meta RankingsMeta `json:"meta,omitzero"`
}
RankingsPage is one page of Client.ListRankings.
func (*RankingsPage) Len ¶ added in v1.1.0
func (p *RankingsPage) Len() int
Len returns the number of records on this page.
type RankingsParams ¶ added in v1.1.0
type RankingsParams struct {
// Player selects the per-player mode (ULTRA): point-in-time records for
// these player ids, up to 50. Leave it empty for the listing mode (PRO).
Player []int64
// AsOf asks for the newest record effective ON OR BEFORE this date,
// "YYYY-MM-DD" — never one dated after it. Empty means the latest known
// record.
AsOf string
// System restricts to one or more ranking systems. The listing mode
// requires exactly one, and [RankingUTR] has no listing (a rating, not a
// ranking).
System []RankingSystem
// ListParams paginates the result.
ListParams
}
RankingsParams filters Client.ListRankings, and selects between its two modes. See that method for the mode rules.
type RateLimit ¶
type RateLimit struct {
// Limit is X-RateLimit-Limit, the ceiling for the current window. nil if
// the header was absent. The FREE tier is 30 requests per minute.
Limit *int
// Remaining is X-RateLimit-Remaining, the requests left in the current
// window. nil if the header was absent.
Remaining *int
// Reset is X-RateLimit-Reset, the instant the current window rolls over.
// The API emits it as unix epoch seconds, not as a delay. The zero value
// means the header was absent; test with Reset.IsZero.
Reset time.Time
// RetryAfter is the Retry-After header as a duration. nil if absent.
//
// Do not read this as proof of throttling: the API sets Retry-After on
// ordinary 2xx responses too, where it merely describes the window. Only
// a 429 (see [ErrRateLimited]) means you were actually limited.
RetryAfter *time.Duration
}
RateLimit is the rate-limit budget the API reported on a single response.
The API sets X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset on every response, and exposes them to browsers via CORS. Each field is a pointer or a zero-testable value because "the header was absent" and "the value was zero" are genuinely different: Remaining of 0 means the budget is spent, while an absent header means nothing was reported at all.
Observe the budget on a successful call with WithRateLimitObserver; on a failed one it is carried by APIError.RateLimit.
func (RateLimit) Known ¶
Known reports whether the response carried any rate-limit information at all.
func (RateLimit) RemainingOr ¶
RemainingOr returns the reported remaining budget, or def if the header was absent. Pass a negative default to keep "unknown" distinct from "exhausted".
type Score ¶
type Score struct {
// Sets is [sets_p1, sets_p2] — sets won by each player.
Sets []int `json:"sets,omitempty"`
// Games is [games_p1, games_p2], each a per-set list. See the type doc.
Games [][]int `json:"games,omitempty"`
// Points is the current game's points as strings: "0", "15", "30", "40",
// "AD". During a tiebreak they are numeric strings. Never integers.
Points []string `json:"points,omitempty"`
// Server is which player is serving, 1 or 2. nil when unknown.
Server *int `json:"server,omitempty"`
// IsTiebreak reports whether the current game is a tiebreak.
IsTiebreak bool `json:"is_tiebreak,omitempty"`
// WinProbabilityP1 is the live model's probability that player 1 wins,
// from 0 to 1. ULTRA only; nil on every lower tier.
WinProbabilityP1 *float64 `json:"win_probability_p1,omitempty"`
// Danger is the live model's danger rating for the leading side. ULTRA
// only; nil on every lower tier.
Danger *float64 `json:"danger,omitempty"`
// Timestamp is when the score was observed. Zero if absent.
Timestamp Time `json:"timestamp,omitzero"`
}
Score is a match score at a point in time.
Two shapes here reliably trip people up:
Games is player-major, not set-major. It is [games_p1, games_p2] where each side is a per-set list, so [[6,3,2],[4,6,1]] reads 6-4, 3-6, 2-1. Use Score.GamesForSet rather than indexing by hand.
Points is a list of strings, not numbers, because tennis scores points as "0", "15", "30", "40" and "AD". Do not parse them as integers.
func (*Score) GamesForSet ¶
GamesForSet returns the games each player won in one set, guarding the player-major layout of Score.Games. ok is false when the set is out of range or the score is nil, which is the normal case for a match that has not reached that set.
Example ¶
Score.Games is player-major, so reading a set means taking one value from each side's list. GamesForSet does that for you.
package main
import (
"fmt"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
score := &livetennisapi.Score{
Games: [][]int{{6, 3, 2}, {4, 6, 1}},
Points: []string{"40", "AD"},
}
for set := range score.NumSets() {
p1, p2, ok := score.GamesForSet(set)
if !ok {
continue
}
fmt.Printf("set %d: %d-%d\n", set+1, p1, p2)
}
fmt.Println(score)
}
Output: set 1: 6-4 set 2: 3-6 set 3: 2-1 6-4 3-6 2-1 (40-AD)
func (*Score) String ¶
String renders the score as "6-4 3-6 2-1 (40-30)". A nil Score, which is what an upcoming match carries, renders as "-".
Example ¶
A nil Score is what an upcoming match carries, and it formats cleanly.
package main
import (
"fmt"
"github.com/livetennisapi/livetennisapi-go"
)
func main() {
var notStarted *livetennisapi.Score
fmt.Println(notStarted)
}
Output: -
type SearchPlayersParams ¶
type SearchPlayersParams struct {
// Search is a name fragment to match. Empty returns the ranked list.
Search string
// ListParams paginates the result.
ListParams
}
SearchPlayersParams filters Client.SearchPlayers.
type Sequence ¶ added in v1.1.0
type Sequence string
Sequence selects which shape of the tape Client.GetMatchTape returns.
const ( // SequenceRaw is every committed row — deliberately non-monotonic, since // independent sources race and a higher-trust one may correct a // lower-trust one backwards. This is the API's default. SequenceRaw Sequence = "raw" // SequenceClean is one row per distinct score state, keeping the last // assertion of each. Only clean rows carry [TapeRow.PointWinner]. SequenceClean Sequence = "clean" )
The tape sequences. An unknown value is rejected with ErrBadRequest and code "bad_sequence".
type SetTiebreak ¶ added in v1.1.0
type SetTiebreak struct {
// P1 is player 1's tiebreak points.
P1 int `json:"p1"`
// P2 is player 2's tiebreak points.
P2 int `json:"p2"`
}
SetTiebreak is the final score of one set's tiebreak.
type StatisticsDescribes ¶ added in v1.1.0
type StatisticsDescribes struct {
GamesP1 []int `json:"games_p1,omitempty"`
GamesP2 []int `json:"games_p2,omitempty"`
TotalGames int `json:"total_games,omitempty"`
}
StatisticsDescribes is the match state a statistics family describes, per upstream: age says when it was fetched, this says WHAT was fetched.
type StatisticsFamily ¶ added in v1.1.0
type StatisticsFamily struct {
// Coverage is "live", "final" (the closing figures of a completed match,
// age nil), "stale", "none" or "diverged".
Coverage string `json:"coverage,omitempty"`
// AsOf is when the family was last updated. Zero if absent.
AsOf Time `json:"as_of,omitzero"`
// AgeSeconds is the family's age. THE TWO FAMILIES USE DIFFERENT CLOCKS
// and their ages must not be compared: the derived age is measured
// against the newest score row (between points there is no new score
// either, so wall-clock age would report staleness that does not exist),
// while the measured age is wall clock, because those are fetched on a
// fixed cadence. nil when unknown.
AgeSeconds *int `json:"age_seconds,omitempty"`
// Describes is the match state the numbers describe. nil when
// unavailable.
Describes *StatisticsDescribes `json:"describes,omitempty"`
}
StatisticsFamily is one family's coverage and age.
type StatisticsFreshness ¶ added in v1.1.0
type StatisticsFreshness struct {
// MeasuredDivergence is nil when the families agree; otherwise it says
// why the measured values were withheld.
MeasuredDivergence *MeasuredDivergence `json:"measured_divergence,omitempty"`
Derived *StatisticsFamily `json:"derived,omitempty"`
Measured *StatisticsFamily `json:"measured,omitempty"`
}
StatisticsFreshness is the per-family coverage and age of a statistics response. Branch on this rather than on the top-level coverage, which only summarises.
type StatisticsSide ¶ added in v1.1.0
type StatisticsSide struct {
// Measured maps measured field names to counts — "aces",
// "double_faults", "first_serves_in", "winners_total" and the rest of the
// upstream vocabulary. Coverage is not uniform and an absent field is
// OMITTED, never zero-filled, so read the keys you are given: a key that
// is present with 0 is a real measured zero, and a key that is absent was
// never measured. Aces and double faults are present across every tour;
// the serve split is absent on ITF singles; winners and unforced errors
// appear on a minority of main-tour matches. A "_of" suffix is the
// denominator of its base field and a "_pct" suffix the percentage.
Measured map[string]int `json:"measured,omitempty"`
ServiceGamesPlayed int `json:"service_games_played,omitempty"`
ServiceGamesWon int `json:"service_games_won,omitempty"`
// HoldPct is nil when no service game was played — never 0, so a present
// 0 is a real measured zero. The same rule holds for every other *int
// percentage here.
HoldPct *int `json:"hold_pct,omitempty"`
ReturnGamesPlayed int `json:"return_games_played,omitempty"`
ReturnGamesWon int `json:"return_games_won,omitempty"`
BreakPct *int `json:"break_pct,omitempty"`
BreakPointsFaced int `json:"break_points_faced,omitempty"`
BreakPointsSaved int `json:"break_points_saved,omitempty"`
BreakPointsSavedPct *int `json:"break_points_saved_pct,omitempty"`
BreakPointsPlayed int `json:"break_points_played,omitempty"`
BreakPointsConverted int `json:"break_points_converted,omitempty"`
BreakPointsConvertedPct *int `json:"break_points_converted_pct,omitempty"`
ServicePointsPlayed int `json:"service_points_played,omitempty"`
ServicePointsWon int `json:"service_points_won,omitempty"`
ServicePointsWonPct *int `json:"service_points_won_pct,omitempty"`
ReturnPointsPlayed int `json:"return_points_played,omitempty"`
ReturnPointsWon int `json:"return_points_won,omitempty"`
ReturnPointsWonPct *int `json:"return_points_won_pct,omitempty"`
PointsPlayed int `json:"points_played,omitempty"`
PointsWon int `json:"points_won,omitempty"`
}
StatisticsSide is one player's in-play statistics, in TWO families that are deliberately not merged.
The typed fields at this level are DERIVED: rebuilt from the point-by-point record. Measured holds counts taken upstream, including the ones no point record can yield — aces, double faults, the serve split, winners and unforced errors. Both families name some of the same quantities, computed two entirely different ways; that is a cross-check, not a duplication to collapse.
type TapeInfo ¶ added in v1.1.0
type TapeInfo struct {
// Coverage says how the tape came to exist. See [Coverage].
Coverage Coverage `json:"coverage,omitempty"`
// Rows is how many rows were observed (watched live). It is not the
// length of the tape you will be served — a reconstructed or mixed tape
// also includes reconstructed rows. Use [TapeMeta.Rows] for that.
Rows int `json:"rows,omitempty"`
// ReconstructedRows is how many reconstructed rows are available.
ReconstructedRows int `json:"reconstructed_rows,omitempty"`
}
TapeInfo is the tape summary carried on each match of a history listing, so a whole page can be qualified in one call instead of one request per match.
type TapeMeta ¶ added in v1.1.0
type TapeMeta struct {
// MatchID is the match the tape belongs to.
MatchID int64 `json:"match_id,omitempty"`
// Rows is how many rows were returned, after any clean-sequence collapse.
Rows int `json:"rows,omitempty"`
// Coverage says how the tape came to exist. See [Coverage].
Coverage Coverage `json:"coverage,omitempty"`
// PointSource is where the rows came from: "observed" (every row watched
// live), "reconstructed" (every row expanded after the fact), "mixed" (a
// reconstructed opening followed by watched rows). Empty on an empty
// tape. Reported once here and never per row.
PointSource string `json:"point_source,omitempty"`
// RawRows is the row count before any collapse; equals Rows for the raw
// sequence.
RawRows int `json:"raw_rows,omitempty"`
// UniqueStates is how many distinct score states the raw tape holds.
// RawRows minus this is pure repetition.
UniqueStates int `json:"unique_states,omitempty"`
// Sequence echoes the requested sequence.
Sequence Sequence `json:"sequence,omitempty"`
// FromArchive reports whether the rows were served from the immutable
// archive rather than the live table. Informational — the content
// contract is identical.
FromArchive bool `json:"from_archive,omitempty"`
// GeneratedAt is when the response was assembled. Zero if absent.
GeneratedAt Time `json:"generated_at,omitzero"`
}
TapeMeta is the coverage metadata beside a tape.
type TapeParams ¶ added in v1.1.0
type TapeParams struct {
// Sequence is [SequenceRaw] (the API's default: every committed row) or
// [SequenceClean] (one row per distinct score state, the only shape that
// carries [TapeRow.PointWinner]).
Sequence Sequence
}
TapeParams selects the shape of Client.GetMatchTape.
type TapeRow ¶ added in v1.1.0
type TapeRow struct {
// Score is the score state this row records, with the same layout and
// nil-safe helpers as everywhere else. On ULTRA the model fields
// ([Score.WinProbabilityP1], [Score.Danger]) are populated per row where
// the model produced them.
Score
// PointWinner is who won the point this row records, 1 or 2 — present
// only on [SequenceClean] rows, and only where the transition from the
// previous row is a single attributable point. nil on gaps, torn rows,
// the first row, and every raw-sequence row (consecutive raw rows are
// corrections, not points). Derived at read time, never stored or
// guessed.
PointWinner *int `json:"point_winner,omitempty"`
}
TapeRow is one row of a match's point-by-point score sequence.
Rows watched live carry a real Timestamp. Rows expanded after the fact from a finished-match point record carry a zero Timestamp and nil model fields, because neither a wall clock nor a model output ever existed for them — nothing is synthesised. A zero Timestamp is the reliable row-level marker of a reconstructed row; the model fields alone are not, since they are stamped best-effort and an observed row may lack them too.
type Thesis ¶
type Thesis struct {
// PickSide is the player the model favours, 1 or 2. nil when unset.
PickSide *int `json:"pick_side,omitempty"`
// Confidence is the model's confidence, from 0 to 1. nil when unset.
Confidence *float64 `json:"confidence,omitempty"`
// WinProbabilityPick is the probability the picked side wins, from 0 to 1.
// nil when unset.
WinProbabilityPick *float64 `json:"win_probability_pick,omitempty"`
// State is how the thesis has held up in play: "valid", "confirmed",
// "weakened" or "broken". Empty when unset.
State string `json:"state,omitempty"`
// Reasoning is the prose argument. Empty when unset.
Reasoning string `json:"reasoning,omitempty"`
// Notes breaks the reasoning into named factors.
Notes ThesisNotes `json:"notes,omitzero"`
// ScenarioPlaybook holds if-then scenarios as raw JSON; the v1 schema does
// not pin their shape.
ScenarioPlaybook json.RawMessage `json:"scenario_playbook,omitempty"`
// CreatedAt is when the thesis was generated. Zero if absent.
CreatedAt Time `json:"created_at,omitzero"`
}
Thesis is the model's directional call on a match.
type ThesisNotes ¶
type ThesisNotes struct {
// Matchup is how the two games interact. Empty when unset.
Matchup string `json:"matchup,omitempty"`
// Environment is surface, altitude, conditions. Empty when unset.
Environment string `json:"environment,omitempty"`
// Fatigue is recent workload. Empty when unset.
Fatigue string `json:"fatigue,omitempty"`
}
ThesisNotes are the named factors behind a Thesis.
type Tier ¶
type Tier string
Tier is a subscription level. A call above your tier is refused with 403.
type Time ¶
type Time struct {
time.Time
// Raw is the exact string the API sent, preserved even when it parsed
// cleanly. Empty when the field was null or absent.
Raw string
}
Time is a timestamp from the API that never fails to decode.
The API documents UTC ISO 8601 with a Z suffix, and date-only fields such as Player.Birthday as a plain calendar date. Both parse into this type. A value the parser does not recognise is kept verbatim in Raw instead of failing the surrounding response, because one odd timestamp is not a reason to lose an entire page of matches.
The zero value means the field was null, absent, or unparseable, so a nullable timestamp needs no pointer:
if !match.ScheduledTime.IsZero() {
fmt.Println(match.ScheduledTime.Local())
}
func (Time) MarshalJSON ¶
MarshalJSON implements json.Marshaler, round-tripping the original string when there was one so re-encoding a response does not silently rewrite it.
func (Time) String ¶
String returns the original string the API sent, or the formatted time when the value was constructed locally.
func (*Time) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler. It returns an error only for JSON that is not a string or null, never for an unrecognised time format.
type Tour ¶
type Tour string
Tour is a circuit accepted by the tour filter on Client.ListMatches and Client.ListFixtures.
Each value covers its singles and doubles draws, so TourATP includes ATP doubles and TourJuniors covers both the boys' and girls' Grand Slam draws.
This is the vocabulary the API accepts as a *filter*, which is not the one it returns in Player.Tour and Fixture.Tour: filtering by TourJuniors yields records whose own tour reads "juniors_boys" or "juniors_girls". Those fields are plain strings for that reason — do not compare a Tour against them.
const ( TourATP Tour = "atp" TourWTA Tour = "wta" TourChallenger Tour = "challenger" TourITF Tour = "itf" TourJuniors Tour = "juniors" )
The tours the filter accepts. An unrecognised value is rejected with a 400 carrying code "bad_tour" rather than silently ignored, so a caller never receives a tour it did not ask for. See APIError.AllowedValues.
type Tournament ¶ added in v1.2.0
type Tournament struct {
// ID is the stable tournament id that match objects carry.
ID string `json:"id,omitempty"`
// Name is the tournament name. Empty when unknown.
Name string `json:"name,omitempty"`
// Tour is the circuit, in the filter's own vocabulary (see [Match.Tour]).
// Empty when the event has no public tour name.
Tour Tour `json:"tour,omitempty"`
// Surface is "hard", "clay" or "grass". Empty when unknown.
Surface string `json:"surface,omitempty"`
// Indoor reports whether the event is played indoors.
Indoor bool `json:"indoor,omitempty"`
// City is the host city, from a curated table. Empty where not curated.
City string `json:"city,omitempty"`
// Country is the host country as ISO-3166 alpha-2 — mind that this is a
// DIFFERENT vocabulary from [Player.Country]'s IOC-style 3-letter codes.
// Empty where not curated.
Country string `json:"country,omitempty"`
// Category is the tournament category where the catalogues agree
// unambiguously on an exact-name join: "grand_slam", "masters_1000",
// "tour_finals", "atp_500", "atp_250", "wta_1000", "wta_500", "wta_250",
// "wta_125", "challenger", "itf" or "juniors". Empty otherwise — never
// derived from the name, because that would be guesswork.
Category string `json:"category,omitempty"`
}
Tournament is one row of the tournament catalogue — the id space Match.TournamentID joins. Identity is one row per tournament and event type, stable across seasons.
type TournamentsParams ¶ added in v1.2.0
type TournamentsParams struct {
// Search is a case-insensitive substring match on the tournament name.
Search string
// Tour restricts results to one circuit. Empty means every tour.
Tour Tour
// ListParams paginates the result.
ListParams
}
TournamentsParams filters Client.ListTournaments.
type Usage ¶ added in v1.2.0
type Usage struct {
// Principal is an opaque reference to your own key.
Principal string `json:"principal,omitempty"`
// Tier is the effective tier, lowercase: "free", "basic", "pro" or
// "ultra". Mind the case — this is the API's own vocabulary here, not
// the uppercase [Tier] constants this package uses for 403 inference.
Tier string `json:"tier,omitempty"`
// BaseTier is the subscription tier; it equals Tier unless a temporary
// grant is active.
BaseTier string `json:"base_tier,omitempty"`
// TierExpiresAt is when a temporary tier grant reverts. Zero when no
// grant is active.
TierExpiresAt Time `json:"tier_expires_at,omitzero"`
// Channel is how the key was issued.
Channel string `json:"channel,omitempty"`
// Limits is the quota grid for this key. Either limit is nil when the
// channel does not enforce it.
Limits struct {
PerMinute *int `json:"per_minute,omitempty"`
PerDay *int `json:"per_day,omitempty"`
} `json:"limits,omitzero"`
// Today is today's usage, current to the second. RemainingDay is nil
// when no daily limit applies.
Today struct {
Calls int `json:"calls,omitempty"`
Errors int `json:"errors,omitempty"`
RemainingDay *int `json:"remaining_day,omitempty"`
} `json:"today,omitzero"`
// History is the last 30 days, oldest first.
History []UsageDay `json:"history,omitempty"`
// AsOf is when this summary was assembled.
AsOf Time `json:"as_of,omitzero"`
}
Usage is the calling key's own usage against its quota, from Client.GetUsage.
The per-minute window is NOT here — it rides on the X-RateLimit-* headers of every response (see RateLimit); this is the durable daily picture. Usage does not carry the daily reset instant either: that arrives as APIError.ResetsAt on the daily 429 itself.
type UsageDay ¶ added in v1.2.0
type UsageDay struct {
// Day is the calendar date.
Day Time `json:"day,omitzero"`
// Calls is how many requests were made that day.
Calls int `json:"calls,omitempty"`
// Errors is how many of them failed.
Errors int `json:"errors,omitempty"`
}
UsageDay is one day of a key's usage history.
type WSChannels ¶ added in v1.1.0
type WSChannels struct {
// Match is the per-match channel template, "match:{id}".
Match string `json:"match,omitempty"`
// Slate is the every-live-score channel, "slate:all".
Slate string `json:"slate,omitempty"`
}
WSChannels is the channel vocabulary of the push WebSocket.
type WSToken ¶ added in v1.1.0
type WSToken struct {
// Token is the signed connection token.
Token string `json:"token,omitempty"`
// ExpiresIn is the token's lifetime in seconds.
ExpiresIn int `json:"expires_in,omitempty"`
// WSURL is the push WebSocket endpoint to connect to.
WSURL string `json:"ws_url,omitempty"`
// Channels is the channel vocabulary.
Channels WSChannels `json:"channels,omitzero"`
}
WSToken is a short-lived connection token for the high-fan-out push feed, from Client.GetWSToken. ULTRA.
Frames on the push feed are the same allowlist score objects the polling endpoints return. Mint a fresh token on every reconnect — the token expires.
type Webhook ¶ added in v1.2.0
type Webhook struct {
// ID is the webhook's id, usable with [Client.DeleteWebhook].
ID int64 `json:"id,omitempty"`
// URL is the delivery endpoint. HTTPS only, publicly routable.
URL string `json:"url,omitempty"`
// Events is what the webhook subscribed to.
Events []WebhookEvent `json:"events,omitempty"`
// Enabled reports whether deliveries are active.
Enabled bool `json:"enabled,omitempty"`
// CreatedAt is when the webhook was registered. Zero if absent.
CreatedAt Time `json:"created_at,omitzero"`
// LastDeliveryAt is when the last delivery succeeded. Zero when none has.
LastDeliveryAt Time `json:"last_delivery_at,omitzero"`
// ConsecutiveFailures counts deliveries that have failed in a row.
ConsecutiveFailures int `json:"consecutive_failures,omitempty"`
// LastError is the most recent delivery error. Empty when none.
LastError string `json:"last_error,omitempty"`
// Secret is the signing secret — present ONLY on the registration
// response from [Client.CreateWebhook], shown exactly once. Store it
// immediately; it is never returned again, not even by
// [Client.ListWebhooks].
Secret string `json:"secret,omitempty"`
// SecretNote is the API's reminder about the secret's one-time nature.
SecretNote string `json:"secret_note,omitempty"`
}
Webhook is one registered outbound webhook. ULTRA, direct keys only.
The API POSTs the same frames the push WebSocket sends to the registered HTTPS endpoint on every matching commit.
type WebhookEvent ¶ added in v1.2.0
type WebhookEvent string
WebhookEvent is a frame kind a webhook can subscribe to.
const ( // WebhookScore delivers a frame on every live score commit. This is the // default when a webhook is registered without events. WebhookScore WebhookEvent = "score" // WebhookBreakPoint delivers break-point frames. WebhookBreakPoint WebhookEvent = "break_point" )
The webhook event kinds.
type WebhookParams ¶ added in v1.2.0
type WebhookParams struct {
// URL is the delivery endpoint. HTTPS only, publicly routable.
URL string `json:"url"`
// Events selects the frame kinds to deliver. Empty means the API's
// default, [WebhookScore] only.
Events []WebhookEvent `json:"events,omitempty"`
}
WebhookParams configures Client.CreateWebhook.