← Developer blog

Free live tennis scores for a trading bot

Pull free live tennis scores for a Polymarket trading bot with curl, Python, and JavaScript. Real endpoints, no card. Auth is one header, X-API-Key.

· · By the Live Tennis API team

Yes, you can feed a Polymarket tennis trading bot with free live scores. Live Tennis API's free tier (30 req/min, 100 req/day, no card) serves live matches, current scores, player rankings, and fixtures. Get a key, send X-API-Key, and poll the current match state.

The free tier gives you the read side, the live state your bot reads to form a signal. Market prices come from Polymarket's keyless Gamma API, and order execution is your own responsibility (more on that at the end). This article covers just the scores: curl, Python, and JavaScript against real endpoints.

What the free tier gives you

Base URL: https://api.livetennisapi.com/api/public/v1. Every request needs the header X-API-Key: <your-key>. Grab a free key at livetennisapi.com/subscribe/free. It is self-serve, no card.

Free-tier endpoints, all of which a live-scores bot needs:

Endpoint Returns
GET /matches?status=live every match in play right now
GET /matches/{id}/score current score for one match
GET /players/{id} a player incl. current ranking & Elo
GET /fixtures upcoming matches
GET /usage your own rate/quota counters

The free tier stops at the current state of a match, the live scoreline as it stands. Completed-match history and point-by-point tape are a paid tier, and you don't need them for a live signal.

curl: is it working?

Smoke-test your API key before writing any code:

curl -s https://api.livetennisapi.com/api/public/v1/matches?status=live \
  -H "X-API-Key: $LIVE_TENNIS_API_KEY"

That returns the live matches. Take an id from the list and read its score:

curl -s https://api.livetennisapi.com/api/public/v1/matches/<match_id>/score \
  -H "X-API-Key: $LIVE_TENNIS_API_KEY"

Python: poll the live board

A small polling loop is all a scores feed needs. Respect the free-tier limits. 30 req/min and 100 req/day means one poll every few seconds at most, so keep the interval sane.

import os, time, requests

BASE = "https://api.livetennisapi.com/api/public/v1"
HEADERS = {"X-API-Key": os.environ["LIVE_TENNIS_API_KEY"]}

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

def score(match_id):
    r = requests.get(f"{BASE}/matches/{match_id}/score",
                     headers=HEADERS, timeout=10)
    r.raise_for_status()
    return r.json()

if __name__ == "__main__":
    while True:
        for m in live_matches():
            print(m.get("id"), score(m["id"]))
        time.sleep(20)  # stay well inside 30 req/min

Fewer, wider calls beat a tight loop over many matches. On the free tier you have a daily budget. One matches?status=live call plus a score read for each in-play match adds up fast. Poll only the matches you care about, and widen the interval when the board is quiet.

JavaScript: the same feed with fetch

Node 18+ (or any modern runtime) has fetch built in, so there are no dependencies:

const BASE = "https://api.livetennisapi.com/api/public/v1";
const HEADERS = { "X-API-Key": process.env.LIVE_TENNIS_API_KEY };

async function liveMatches() {
  const r = await fetch(`${BASE}/matches?status=live`, { headers: HEADERS });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return (await r.json()).data;   // list endpoints return {data: [...], meta: {...}}
}

async function score(matchId) {
  const r = await fetch(`${BASE}/matches/${matchId}/score`, { headers: HEADERS });
  if (!r.ok) throw new Error(`HTTP ${r.status}`);
  return r.json();
}

const matches = await liveMatches();
for (const m of matches) {
  console.log(m.id, await score(m.id));
}

Watch your quota

Never guess whether you're near the limit. Read it. GET /usage returns your own counters, and a 429 means you've hit the rate cap, so back off and retry:

u = requests.get(f"{BASE}/usage", headers=HEADERS, timeout=10).json()
print(u)  # remaining req/min and req/day for your key

Where this plugs into a Polymarket bot

Live scores are one half. The other half is market prices, which come from Polymarket's public Gamma API (https://gamma-api.polymarket.com). It is keyless, and tennis markets carry tag id 864. Our API is not needed for prices. Gamma is the source.

Joining the two, a live scoreline next to the matching market's price, is exactly what the open-source livetennisapi/polymarket-tennis toolkit (MIT) does. It discovers tennis markets on Gamma, matches them to live matches, and pairs price with score. It is observe-only: no order execution, no wallets, no keys.

Placing orders (wallets, private keys, the CLOB client) is the builder's own responsibility.

If you go there, Polymarket's official py-clob-client is the tool, and none of this is financial advice or a guarantee of anything. Start with the read side, which is what's free.

Next: the pillar walk-through, How to build a Polymarket tennis trading bot (Python), stitches discovery, matching, and this live-scores feed together end to end.

Frequently asked questions

Is there a free API for live tennis scores?

Yes. Live Tennis API's free tier serves live matches (GET /matches?status=live) and current scores (GET /matches/{id}/score). It is 30 req/min, 100 req/day, no card. It also serves player rankings, Elo, and fixtures. Get an API key at livetennisapi.com/subscribe/free and send it as the header X-API-Key. The base URL is https://api.livetennisapi.com/api/public/v1.

Can I use free tennis scores for a Polymarket trading bot?

Yes. A bot's read side works on the free tier. Market prices come separately from Polymarket's keyless Gamma API (tennis tag id 864). The open-source polymarket-tennis toolkit joins the two halves and is observe-only.

What are the free-tier rate limits?

30 requests per minute and 100 requests per day. Poll one live-board call plus a score read per in-play match, keep the interval around 20 seconds, and check GET /usage for your remaining budget. A 429 response means you hit the cap, so back off and retry.

What does the free tier NOT include?

The free tier stops at the current state of a match. Completed-match history and point-by-point tape are on the paid Basic tier. Market prices/events are on Pro. Win-probability and the WebSocket stream are on Ultra. A live-scores feed for a bot needs none of those, just the free current-state endpoints.

How do I authenticate with Live Tennis API?

Send your API key on every call in the header X-API-Key. For example: curl -H "X-API-Key: <your-key>" https://api.livetennisapi.com/api/public/v1/matches?status=live. There is no OAuth flow and no query-string token. The header is the one auth method.

Built with the Live Tennis API, which serves 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