← Developer Blog

Kalshi Tennis: Live Scores, Series Tickers & API (2026)

How tennis works on Kalshi in 2026: match series (ATP, WTA, Challenger, ITF, set winners), ticker format, keyless API reads, the rules, and a live-score method.

· By the Live Tennis API team

Kalshi lists tennis as a family of series: a market per player per match (KXATPMATCH, KXWTAMATCH, KXITFMATCH, KXATPCHALLENGERMATCH, KXWTACHALLENGERMATCH), set winners (KXATPSETWINNER), game totals, tiebreak and exact-score props, plus tournament futures. On 2026-08-23 the public API showed 34 open ATP, 26 WTA, 46 ITF, 70 ATP Challenger and 20 WTA Challenger match markets; the Cincinnati men's final (Fils vs Tiafoe) had $136k and $166k of contract volume on its two sides. Reads are keyless. Below: the ticker format, how to read prices and rules from the API, what the live-score side needs, and the settlement rules you must not hard-code.

Everything here is read-only plumbing, not advice. Execution on Kalshi needs your own authenticated API key and is your responsibility.

The tennis series on Kalshi (2026-08-23)

Series ticker What it is Open markets seen
KXATPMATCH ATP match winner, one market per player 34
KXWTAMATCH WTA match winner 26
KXITFMATCH / KXITFWMATCH ITF men's / women's match winner 46 (men)
KXATPCHALLENGERMATCH / KXWTACHALLENGERMATCH Challenger-level match winner 70 / 20
KXATPSETWINNER / KXWTASETWINNER Set winner 68 (ATP)
KXATPGAMETOTAL, KXATPTIEBREAK, KXATPEXACTMATCH, KXATPTOTALSETS Game totals, tiebreak-to-occur, exact set score, total sets varies by day
KXATP / KXWTA, KXUSOPEN, KXWIMMEN, … Tournament winner futures

Counts are from GET /trade-api/v2/markets?series_ticker=…&status=open on 2026-08-23; they change daily with the calendar. The full list of series (tennis included) is at GET /trade-api/v2/series?category=Sports.

Ticker anatomy

KXATPMATCH-26AUG23FILTIA-TIA
│          │        │      └─ outcome: the player this market pays on (TIA = Tiafoe)
│          │        └─ event: FILTIA = Fils vs Tiafoe
│          └─ date: 26AUG23 = 2026-08-23
└─ series

Each match is one event (KXATPMATCH-26AUG23FILTIA) with one market per player (…-FIL, …-TIA). The two markets are priced independently, so the bids do not have to sum to exactly $1.00 — on the Cincinnati final they were 0.66/0.67 (Fils) and 0.33/0.34 (Tiafoe).

Read prices, volume and rules — keyless

Kalshi's public market reads need no key. Prices come back in dollar strings (yes_bid_dollars, yes_ask_dollars, last_price_dollars), volume as volume_fp, and the settlement wording in rules_primary / rules_secondary.

import requests

K = "https://api.elections.kalshi.com/trade-api/v2"

def kalshi_tennis(series="KXATPMATCH", limit=50):
    r = requests.get(f"{K}/markets", params={"series_ticker": series, "status": "open", "limit": limit}, timeout=10)
    r.raise_for_status()
    return r.json()["markets"]

for m in kalshi_tennis():
    print(m["ticker"], m["yes_sub_title"],
          "bid", m["yes_bid_dollars"], "ask", m["yes_ask_dollars"],
          "vol", m["volume_fp"], "closes", m["close_time"])
    # the rule text for THIS market — read it, never assume it
    # print(m["rules_secondary"])

The order book is also public: GET /trade-api/v2/markets/{ticker}/orderbook returns orderbook_fp with yes_dollars / no_dollars price levels and sizes.

The rules: pre-start vs after a ball is played

From the ATP match market's own rules_secondary, retrieved 2026-08-23:

If the match does not occur (signaled by a ball being played) due to a player injury, walkover, forfeiture, or any other cancellation (all before the match starts), the market will resolve to a fair price in accordance with the rules. If this match is postponed or delayed, the market will remain open and close after the rescheduled match has finished (within two weeks).

The ITF series says something different:

… all markets will resolve to $0.50. If a player withdraws or forfeits after a match has started, that player will resolve to No.

So the two things a tennis trader on Kalshi must track are whether a ball has been played (the line between "fair price"/$0.50 and a normal result) and who retired. Side-by-side wording for Kalshi, polymarket.com and Polymarket US is in Polymarket & Kalshi tennis retirement/walkover rules (2026).

The live-score method

Kalshi's API tells you the price; it does not tell you the score. The method that works is to hold the Kalshi market next to the live match state and act on the state, not on the price alone:

  1. Map the ticker to a match. Split the event ticker into date + two surname fragments (26AUG23 + FIL/TIA) and match against today's fixtures by date and surname prefix; refuse to match when two candidates fit (shared surnames happen every week on the ITF tour).
  2. Read live state on a schedule your tier allows. The Live Tennis API free tier (30 req/min, 100 req/day, no card) returns live matches with score line, server and break-point state; Ultra adds a push feed so you are not polling at all. Key at livetennisapi.com/subscribe/free.
  3. Watch the fields that change settlement: outcome (retired, walkover, default, abandoned, completed; null until settled), event_status (Retired, Walk Over, Cancelled, Postponed, Interrupted) and withdrew. The lifecycle status only ever says upcoming / live / completed / cancelled.
  4. Compare, then decide in your own code. What you do with a break point against the favourite or a retirement is your strategy; the data layer's job is to be right and early.
import os, requests

LTA = "https://api.livetennisapi.com/api/public/v1"
H = {"X-API-Key": os.environ["LIVETENNIS_API_KEY"]}

def live_board():
    r = requests.get(f"{LTA}/matches", params={"status": "live"}, headers=H, timeout=10)
    r.raise_for_status()
    return r.json()["data"]          # list endpoints return {"data": [...], "meta": {...}}

def surname(name: str) -> str:
    return name.split()[-1].upper()

def find_match(event_ticker: str, board):
    # KXATPMATCH-26AUG23FILTIA -> 'FILTIA' -> ('FIL', 'TIA')
    frag = event_ticker.split("-")[1][7:]
    a, b = frag[:3], frag[3:]
    hits = [m for m in board
            if {surname(m["players"]["p1"]["name"])[:3], surname(m["players"]["p2"]["name"])[:3]} == {a, b}]
    return hits[0] if len(hits) == 1 else None   # ambiguous or absent -> None, never a guess

for m in kalshi_tennis():
    match = find_match(m["event_ticker"], live_board())
    if match:
        g = match.get("games") or []          # player-major: [[p1 games per set], [p2 games per set]]
        line = " ".join(f"{a}-{b}" for a, b in zip(*g)) if g else match.get("status")
        print(m["ticker"], m["yes_bid_dollars"], "|", line,
              "| serving:", match.get("server"), "| outcome:", match.get("outcome"), match.get("event_status"))

The three-letter surname prefix is a heuristic that breaks on shared prefixes; the polymarket-tennis toolkit's matching module (full-name folding, date gating, explicit confidence, None on ambiguity) is the robust version and is venue-agnostic — point it at a Kalshi event ticker the same way.

How fast is Kalshi's tennis data — and how fast is the score?

These are two different latencies. Kalshi's API reflects its own order book immediately; the question traders actually ask is how far behind the court the score is. In the Live Tennis API an in-play change (point, break, retirement) is stamped on the live ingest cycle — seconds after the feed states it — while a walkover declared before first serve is picked up by a short pre-start status poll, because no live socket can push a match that never went live. We do not quote a single number here because it differs by tour and by case; the per-match event_status_updated_at timestamp lets you measure it yourself.

Related

Frequently asked questions

Does Kalshi have tennis markets?

Yes. Kalshi lists per-player match-winner markets for ATP (KXATPMATCH), WTA (KXWTAMATCH), ITF (KXITFMATCH, KXITFWMATCH) and Challenger events, plus set winners, game totals, tiebreak and exact-score props, and tournament futures. On 2026-08-23 the public API showed 34 open ATP, 26 WTA, 46 ITF men's and 70 ATP Challenger match markets.

Can I read Kalshi tennis prices without an API key?

Yes. GET https://api.elections.kalshi.com/trade-api/v2/markets?series_ticker=KXATPMATCH&status=open returns open markets with yes_bid_dollars, yes_ask_dollars, last_price_dollars, volume_fp and the rules text, and /markets/{ticker}/orderbook returns the order book — no authentication needed for reads. Trading requires your own authenticated key.

How are Kalshi tennis tickers structured?

SERIES-DATE+EVENT-OUTCOME, e.g. KXATPMATCH-26AUG23FILTIA-TIA: series KXATPMATCH, date 26AUG23 (2026-08-23), event FILTIA (Fils vs Tiafoe), outcome TIA (the market that pays if Tiafoe wins). Each match is one event with one market per player.

What happens on Kalshi if a tennis match is a walkover?

Per the market's rules_secondary (2026-08-23): on ATP/WTA match markets a match that does not occur before a ball is played (injury, walkover, forfeiture, cancellation) resolves to a fair price in accordance with the rules; on ITF match markets it resolves to $0.50, and an ITF player who withdraws or forfeits after the match has started resolves to No. Postponed matches stay open up to two weeks.

Does Kalshi provide live tennis scores?

No — the Kalshi API returns prices, volume, order books and rules, not the score. Pair it with a live tennis feed such as the Live Tennis API (free tier: live matches, score line, server, break-point state, plus outcome/event_status fields for retirements and walkovers) and match the event ticker to the live match by date and player names.

Built with the Live Tennis API — real-time scores, players, odds and model win-probability for ATP, WTA, Challenger and ITF.

API reference SDKs on GitHub Plans from $9.99/mo