Build a tennis draw and tournament page
Season calendar, round-by-round draws for any edition, and the tournament's matches with statistics
1. The calendar
One row per edition with dates, surface, tier, city and draw size. The year has 1,000+ editions per tour, so page with limit and page and filter on category in your code (for example grand_slam, masters_1000, atp_tour).
curl "https://api.citoapi.com/api/v1/tennis/tournaments/calendar?year=2026&tour=ATP&limit=100&page=1" -H "x-api-key: YOUR_API_KEY"
# items[]: id, name, start_date, end_date, surface, category, tier ("ATP 250"), city, country_code, draw_size, indoor
curl "https://api.citoapi.com/api/v1/tennis/tournaments/upcoming?tour=WTA&limit=20" -H "x-api-key: YOUR_API_KEY" # in progress or next2. The draw
rounds runs from the final back to the first qualifying round. Each match carries both players with seed and entry (Q, WC, LL, PR, SE, ALT), score, winner_id, bracket_position (restarts at 1 each round) and next_match_id for drawing the lines between rounds.
const BASE = "https://api.citoapi.com/api/v1/tennis";
const headers = { "x-api-key": process.env.CITO_API_KEY };
const draw = await fetch(`${BASE}/tournaments/atp_2025_540/draw`, { headers }).then((r) => r.json());
// Main draw only, first round first: R128 -> F
const main = draw.rounds.filter((r) => !r.round_code.startsWith("Q")).reverse();
for (const round of main) {
console.log(round.round_name);
for (const m of [...round.matches].sort((a, b) => a.bracket_position - b.bracket_position)) {
const p = (x) => `${x.seed ? "[" + x.seed + "] " : ""}${x.entry ? "(" + x.entry + ") " : ""}${x.name}`;
console.log(` ${p(m.player1)} v ${p(m.player2)} ${m.score ?? m.status}`);
}
}
// draw.summary: status, main_draw_matches_completed / _pending, rounds_in_progress- Any past edition:
/tournaments/{id}/editions/{year}/drawfollows the event across years, for example/tournaments/atp_2025_540/editions/1985/draw. - Grand Slam ids for any year:
{tour}_{year}_{code}with 580 Australian Open, 520 Roland Garros, 540 Wimbledon, 560 US Open. - Doubles are not in the bracket; read them from
/matches?tournament_id=...&match_type=doubles. surface_badgegives a label and colour for the surface chip.
3. Order of play and match statistics
The schedule lists the edition's matches in date and round order. Add include=stats to show serve statistics on finished matches without one call per match.
curl "https://api.citoapi.com/api/v1/tennis/tournaments/atp_2025_540/schedule?include=stats&page_size=100" -H "x-api-key: YOUR_API_KEY"4. Keep it live during the event
Refresh the draw when a match in the event finishes instead of on a timer: subscribe to tennis.match.completed filtered to the tournament and re-read /draw on each event. For live scores inside the bracket, use the live scoreboard stream filtered by match.
Call budget
- Calendar for a season: 10 to 11 calls per tour at 100 per page. Cache for a day.
- A finished edition's draw never changes: cache it permanently.
- A live edition: 1 call per finished match if you refresh on webhooks.
Open the 2025 Wimbledon draw
Every round from qualifying to the final, with seeds, entry status, scores and where each winner goes next.
curl "https://api.citoapi.com/api/v1/tennis/tournaments/atp_2025_540/draw" \
-H "x-api-key: YOUR_API_KEY"