← 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, X-API-Key auth.

· · 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 — 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 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 are better than 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, so poll only the matches you actually 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 — 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; 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) — 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 (30 req/min, 100 req/day, no card) serves live matches (GET /matches?status=live) and current scores (GET /matches/{id}/score), plus player rankings, Elo, and fixtures. Get a key at livetennisapi.com/subscribe/free and send it as the X-API-Key header. 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 — the live score it turns into a signal — works on the free tier. Market prices come separately from Polymarket's keyless Gamma API (tennis tag id 864). The two halves are joined by the open-source polymarket-tennis toolkit, which 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 — 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 key in the X-API-Key request header on every call, e.g. 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 only auth method.

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