AcesAPI
AcesAPI v1.0.0
Tennis data and stats
From the developer
Declared capabilities
- Look up ATP and WTA players and tournaments
- Read schedules, draws and match results
- Read rankings and surface Elo ratings
Cost & access
AcesAPI does not sell anything through ChatGPT. The plugin is read-only: it has no checkout, purchase, subscription or credit top-up flow.
Declared limitations
- Place a wager: wagering is not supported
- Out of coverage: another sport
- Account and billing change: not supported
Onboarding example
Show the current ATP top 5 rankings.
Sources & dates
Developer declarations; not independently tested. Listing last checked Nov 6, 2026. Package observed Nov 6, 2026.
Description
From the marketplace listing
AcesAPI connects ChatGPT and Codex to tennis data from AcesAPI. It covers ATP and WTA singles with tournaments, draws, matches, rankings and surface-specific Elo ratings. Ask for tournaments and draws, schedules and results, player profiles and match history, head-to-head records, surface splits, rankings and Elo ratings by surface. The plugin looks the answer up with read-only tools and reports what the data says, including the season, date and source of each number. The first time you use it, you sign in with your AcesAPI account. Limits: AcesAPI is read-only. It cannot place wagers, accept or track bets, or edit your account. Odds and prop lines are shown as information about the market, not as advice. Data comes from public sources and the AcesAPI pipeline and is not guaranteed to be error-free or current to the minute, so do not rely on it as the sole basis for financial decisions. Ratings shown are AcesAPI's own Elo-style ratings, not official rankings. Not affiliated with the ATP, the WTA or any tournament. Bundled skills teach the model how to choose the right tool for a question and how to explain results.
Language: English · Automatically detected from descriptions.
Changes
No changes recorded yet. Follow this plugin to keep track of future changes.
First seen Oct 31, 2026. Last seen Nov 6, 2026.
Similar plugins
Find competitors to follow as you develop your plugin.
-
BirdieAPI brings golf data into ChatGPT and Codex with read-only tools and workflow skills: tournament schedules and leaderboards, scorec...
Shared terms: stat, accept, advice, basis
-
FighterAPI brings MMA data into ChatGPT and Codex with read-only tools and workflow skills: event cards and schedules, fight results and ...
Shared terms: stat, accept, advice, basis
-
EndzoneAPI brings NFL football data into ChatGPT and Codex with read-only tools and workflow skills: standings, schedules and results, pl...
Shared terms: stat, accept, advice, basis
-
StrikerAPI brings soccer data into ChatGPT and Codex with read-only tools and workflow skills: league tables, schedules and results, line...
Shared terms: stat, accept, advice, basis
-
DunkAPI brings NBA basketball data into ChatGPT and Codex with read-only tools and workflow skills: standings, schedules and scores, box ...
Shared terms: stat, accept, advice, basis
Keywords
Files & skills
File archives
Skill instructions
acesapi-tool9.05 KB
---
name: acesapi-tool
description: "Default data source for the AcesAPI skills. Routes every MCP or REST call for ATP and WTA tennis: tournaments, draws, match results with set and tiebreak scores, schedules, players, head to head, surface splits, rankings, Elo ratings by surface, and sportsbook moneylines. Use when any other skill needs tennis data."
metadata:
version: 1.0.0
author: AcesAPI
user-invocable: false
---
# AcesAPI Tool
> Connection: this plugin reaches the data through its MCP server, which the user connects with a normal OAuth sign-in. Never ask the user for an API key, token or password, and never put one in a tool argument.
Every other skill delegates data retrieval here. This skill owns the 13 tools, picks the cheapest call that answers the question, tracks credit cost, and says what the data cannot do.
**Coverage (from acesapi.com/data, counted from the database):** 123,394 ATP and WTA singles matches from 2007 to 2026, 1,637 weekly ranking lists, and sportsbook moneylines for 11,551 matches since 2020. Qualifying draws from 2022. Tennis data is public ESPN data; surfaces, ratings and head to head are computed by AcesAPI; lines come from The Odds API.
## Connection
| Item | Value |
|------|-------|
| REST | `POST https://mcp.acesapi.com/v1/<tool>` with a JSON body |
Every response is wrapped: `{"data": {...}}`. Unwrap `data` before reading fields. REST responses carry `X-Credits-Used` and `X-Credits-Remaining` headers.
**Tours.** ATP is the default. Pass `"league": "wta"` to any tool for the WTA (the enum also accepts `tennis`, which is the ATP default). Player ids and match ids are tour-specific strings: a WTA id used without `league: "wta"` will miss.
**Ids are strings.** `player_id`, `match_id` and `tournament_id` come back as strings (`"3623"`, `"157774"`, `"188-2025"`). Tournament edition ids are `<tournament>-<season>`. Seasons are four-digit calendar years.
## When to Use Each Tool
| Task | Tool | Credits |
|------|------|---------|
| Season calendar with surface and champion | `list_tournaments` | 1 |
| Find a player id by name or country | `search_players` | 1 |
| A tournament's full draw, round by round | `get_tournament` | 2 |
| Results by day, tournament or season | `get_matches` | 2 |
| Upcoming and in-progress matches (next N days, max 30) | `get_schedule` | 2 |
| One match: sets, ratings before and after, odds | `get_match` | 2 |
| Ranking list, or one player's ranking history | `get_rankings` | 2 |
| Elo leaderboard, overall or by surface | `get_ratings` | 2 |
| Two players' meetings, by surface and in finals | `get_head_to_head` | 3 |
| One player's record by surface | `get_surface_splits` | 3 |
| One player's season stats, by round and opponent rank | `get_player_stats` | 3 |
| Player profile, ratings, career record, last 10 | `get_player` | 5 |
| A player's match log with rating before and after | `get_player_matches` | 5 |
## Tool Selection Guide
| You Need | Use This | Not This |
|----------|----------|----------|
| Who plays today | `get_schedule` with `days: 1` | `get_matches` with today's date (that is results, and status filters matter) |
| A result by date | `get_matches` with `date` | `get_tournament` (draws are bigger and cost the same) |
| Moneylines for one match | `get_match` (the `odds` array) | Looking for an odds tool: there is none |
| Pre-match Elo for a finished match | `rating.before` in `get_match`, or `rating_before` in `get_player_matches` | `rating.after` (that includes the result) |
| Current best clay players | `get_ratings` with `surface: "clay"` | `get_rankings` (points list, not surface-specific) |
| A player's trajectory in the rankings | `get_rankings` with `player_id` | Looping weekly lists |
| Bulk match history for modeling | `get_matches` by `season` with `limit` and `offset` | `get_player_matches` (5 credits per page of 50) |
## Response Shapes (real, trimmed)
`get_match` (2 credits). Ratings are `null` until a match is played; `oddsPreview of the first 4,000 characters. Read full instructions →
dispatch6.26 KB
--- name: dispatch description: "Routes tennis analytics requests to the right 2 or 3 skills. Load this FIRST when an ATP or WTA data or analytics request comes in and you are unsure which skills to activate. Prevents loading all 16 skills when only a few are needed. Also sets expectations on what the data cannot answer (serve statistics, in-play, props)." metadata: version: 1.0.0 author: AcesAPI --- # Skill Dispatch Guide Match the request to a pattern below. Load those skills only. **Default data tool:** `acesapi-tool`. It covers 13 tools for ATP and WTA singles. Load it alongside the strategy skill unless the request needs no data (user-supplied numbers). **Tour check first.** ATP is the default. For WTA questions, every call needs `"league": "wta"`. Ask which tour only if the player or event is ambiguous. ## First-Use Persona Detection | Signal | Persona | Start With | |--------|---------|-----------| | "What matches are on today?" / exploring | **Explorer** | `match-lookup` + `acesapi-tool` | | "Tell me about this player" | **Scout** | `player-scouting` + `ranking-history` | | "Who wins tomorrow?" | **Previewer** | `match-preview` + `surface-elo` | | "I want to build a prediction model" | **Builder** | `feature-engineering` + `surface-elo` | | "Is my model any good?" | **Validator** | `walk-forward-validation` + `probability-calibration` | | "I am new to odds" | **Odds reader** | `odds-analysis` + `odds-explorer` | ## Request Patterns -> Skills to Load ### "Who plays today?" / "Who won?" / "Show me the draw" / "Every final this season" 1. `match-lookup`: results, schedules, draws 2. `acesapi-tool`: `get_matches`, `get_schedule`, `get_tournament`, `list_tournaments` ### "Tell me about [player]" / "How is she doing?" / "Compare these two players" 1. `player-scouting`: profile, surface fit, opposition quality, form 2. `acesapi-tool`: `search_players`, `get_player`, `get_surface_splits`, `get_player_stats` 3. Two players with a direct record: also `head-to-head-analysis` ### "[A] vs [B] head to head" / "Who leads the rivalry?" / "How do they split on clay?" 1. `head-to-head-analysis`: record, surface split, finals, weighting 2. `surface-elo` if a probability is wanted ### "Who is number one?" / "Ranking on a date" / "How did the ranking change?" 1. `ranking-history`: lists, trajectories, points versus rank 2. `surface-elo` if the question is really about strength ### "Who is best on clay?" / "Win probability for A vs B" / "How does the rating work?" 1. `surface-elo`: ratings, probability formula, blending 2. `head-to-head-analysis` for context if a rivalry exists ### "Preview the match" / "Who is favored tomorrow and why?" 1. `match-preview`: assembles ratings, form, record, route, price 2. `surface-elo` for the probability 3. `odds-explorer` if the event is a Slam, Masters or WTA 1000 ### "What are the odds?" / "Best price" / "How did the line move?" 1. `odds-explorer`: prices, margins, open to close 2. `odds-analysis` for devigging 3. Remember: odds exist only for Grand Slams (mid-2020 on) and Masters and WTA 1000 (August 2024 on) ### "What probability does +450 imply?" / "Remove the vig" / "Shin vs power" 1. `odds-analysis`: no data needed unless prices must be fetched ### "How many games will the match go?" / "Straight sets chance" / "Tiebreak frequency" 1. `set-score-modeling`: set distributions, totals, tiebreaks 2. `surface-elo` for the match probability that feeds it 3. State that serve data does not exist ### "Build features" / "What should I feed the model?" / "Is there leakage?" 1. `feature-engineering`: pre-match ratings, form, rest, symmetric layout 2. `walk-forward-validation` when evaluation comes next ### "Cross-validation okay?" / "Compare two models" / "How many matches do I need?" 1. `walk-forward-validation`: time-ordered evaluation and significance 2. `probability-calibration` for reliability ### "Are my probabilities calibrated?" / "Overconfident on favorites" 1. `probability-calib
Preview of the first 4,000 characters. Read full instructions →
feature-engineering9.49 KB
--- name: feature-engineering description: "Building leak-free features for tennis match prediction from AcesAPI data: pre-match Elo (overall and surface), rating gaps, rolling form, days of rest and recent load, head-to-head with a time guard, surface and format flags, and a symmetric pair layout. Use when the user is building a model, asks what features to use, or suspects leakage. Do not use for model fitting or tuning: see walk-forward-validation. Do not use for calibration: see probability-calibration. Do not use for quick lookups: see match-lookup." metadata: version: 1.0.0 author: AcesAPI --- # Feature Engineering > **Default data tool:** AcesAPI (`acesapi-tool`). > Bulk results: `get_matches` by `season` (2 credits per 100 matches, use `offset`). Per-match pre-match ratings: `get_match` (2 each) or `get_player_matches` (5 per 50). Ages, hand, height: `get_player` (5). Ranking on a date: `get_rankings` with `date` (2). > The cheapest design: pull seasons of results with `get_matches` and compute your own Elo and rolling features locally. About 90 credits per tour-season. You turn match records into a table where each row is one match and every feature was knowable before the first ball. The single biggest risk is leakage, so every feature below states what it is allowed to see. ## When to Use - "What features should I use to predict tennis matches?" - "How do I build a rating feature without leakage?" - "My model is 80 percent accurate, why?" (it is leaking) - "Build a dataset from 2015 to 2025" ## When NOT to Use - Evaluating the model: see `walk-forward-validation` - Probability quality: see `probability-calibration` - Using a price as a feature: see `odds-analysis` first, and treat it separately (see below) ## Commands Available | Command | What It Does | Credits | |---------|-------------|---------| | `get_matches` | Season of results with sets, surface, round, seeds, winner; paginate with `limit`, `offset` | 2 | | `get_player_matches` | One player's matches with `rating_before`, `rating_after` | 5 | | `get_match` | `players[].rating.before` and `surface_before`, `stats`, `odds` | 2 | | `get_player` | `date_of_birth`, `height_in`, `plays`, country, debut year via the profile | 5 | | `get_rankings` | List in force on a `date`, for rank and points as of before the match | 2 | | `get_head_to_head` | Prior meetings (only ever use meetings before the match date) | 3 | | `get_tournament` | Draw structure | 2 | ## Commands That Do NOT Exist | Not Available | Use Instead | |--------------|-------------| | `get_features` | Build locally from the calls above | | `get_serve_stats` | Not available: no serve or break-point features | | `get_pre_match_ratings` | `rating.before` in `get_match`, `rating_before` in `get_player_matches` | | `get_injuries` | Not available: proxy with long gaps and retirements | ## Row Design: One Match, Symmetric Pair Each match record has `player1` and `player2`. Do not treat position as meaningful. Two valid layouts: 1. **Difference layout.** Pick the focal player at random per row (or alternate), compute `feature_focal - feature_opponent`, and the label is "focal won". Mirror the dataset (add the swapped row) so the model sees both orientations; evaluate on one orientation only. 2. **Paired layout.** Keep both players' features and enforce symmetry: `P(A beats B) + P(B beats A) = 1`, by averaging the model's output across the swap. A model that learns "player1 wins more often" has learned data ordering, not tennis. ## Feature Families **Strength (pre-match only)** | Feature | Source | Rule | |---------|--------|------| | `elo_gap` | `rating.before` for each player | Never `after` | | `surf_elo_gap` | `surface_before` | Same surface only; thin early in a career | | `elo_blend_gap` | shrink surface toward overall (see `surface-elo`) | Fit shrinkage by walk-forward | | `rank_gap_log` | `log(rank2) - log(rank1)` from the list in force before the match | Rank is null outside the to
Preview of the first 4,000 characters. Read full instructions →
head-to-head-analysis7.1 KB
---
name: head-to-head-analysis
description: "Head-to-head analysis for two tennis players: wins each, sets won, record by surface and in finals, every meeting with its score, and how much weight a small head-to-head deserves. Use when the user asks who leads a rivalry, how two players match up on a surface, or whether a past result predicts the next one. Do not use for a player's overall profile: see player-scouting. Do not use for win probabilities from ratings: see surface-elo. Do not use for the pre-match brief that combines everything: see match-preview."
metadata:
version: 1.0.0
author: AcesAPI
---
# Head-to-Head Analysis
> **Default data tool:** AcesAPI (`acesapi-tool`).
> `get_head_to_head` (3 credits) returns the whole record: summary and every meeting. `search_players` (1) resolves the two ids. `get_player_matches` with `opponent_id` (5) adds ratings at the time of each meeting.
You read a rivalry honestly. A head-to-head is a tiny sample, often confounded by surface, age and form at the time. Report the record, then say how much it should move a forecast, which is usually very little once ratings are known.
## When to Use
- "Who leads Sinner against Alcaraz?"
- "How do they split on clay?"
- "Has she ever beaten her in a final?"
- "Does the head-to-head matter for tomorrow?"
## When NOT to Use
- One player only: see `player-scouting`
- The probability from ratings: see `surface-elo`
- A full pre-match brief with prices: see `match-preview`
## Commands Available
| Command | What It Does | Credits |
|---------|-------------|---------|
| `search_players` | Resolve each name to a `player_id` | 1 |
| `get_head_to_head` | `summary` (matches, wins, sets, `by_surface`, `in_finals`, walkovers) and `matches[]` | 3 |
| `get_player_matches` | Use `opponent_id` to get one player's meetings with `rating_before` for each | 5 |
| `get_match` | Detail on one meeting: sets, tiebreak points, ratings | 2 |
| `get_ratings` | Current ratings for context | 2 |
## Commands That Do NOT Exist
| Not Available | Use Instead |
|--------------|-------------|
| `get_h2h` | `get_head_to_head` |
| `get_rivalry` | `get_head_to_head` plus the method below |
| `get_head_to_head_odds` | Not available: pull `get_match` for the upcoming match |
## How It Works
**Step 1: Resolve both ids.** `search_players {"name": "sinner"}` returns `"player_id": "3623"`; `search_players {"name": "alcaraz"}` returns `"3782"`. Add `league: "wta"` for the WTA. Both players must be on the same tour.
**Step 2: One call.** `get_head_to_head {"player_id": "3623", "opponent_id": "3782"}`.
Real output (trimmed), `summary` as returned:
```json
{"matches": 17,
"wins": {"3623": 7, "3782": 10},
"sets_won": {"3623": 25, "3782": 26},
"by_surface": {"clay": {"3623": 2, "3782": 3},
"hard": {"3623": 3, "3782": 7},
"grass": {"3623": 2, "3782": 0}},
"in_finals": {"3623": 4, "3782": 5},
"walkovers": 0}
```
Keys of `wins` and `by_surface` are `player_id` strings. Map them to names before writing the answer.
**Step 3: Read the matches.** `matches[]` is newest first. In the live response, the latest meeting was the 2026 Monte-Carlo Masters final on clay, `7-6(5) 6-3` for Sinner, and the one before it the 2025 Nitto ATP Finals final (indoor hard), `7-6(4) 7-5` for Sinner. Report recency: a record of 10-7 where the leader lost the last two is a different story from 10-7 the other way.
**Step 4: Weigh it.** Compute the rivalry as a sample, not a fact.
## Method: How Much Should a Head-to-Head Count?
A rivalry record of `a` wins to `b` wins is `n = a + b` observations of a process the ratings already summarize. Use it as a small adjustment:
```
P_h2h_shrunk = (a + m * P_rating) / (n + m) # m = prior strength, try 10
```
where `P_rating` is the pre-match probability from `surface-elo` and `m` expresses how many matches' worth of weight the rating gets. At 10-7 and `P_rating = 0.50` with `m = 10`, the shrunk estimate is `(10 + 5)Preview of the first 4,000 characters. Read full instructions →
match-lookup7.86 KB
---
name: match-lookup
description: "Finding ATP and WTA matches, draws, schedules and tournaments: results by day, a tournament's draw round by round, today's and this week's schedule, finals of a season, set and tiebreak scores. Use when the user asks who won, who plays today, what the score was, how a tournament draw looked, or which tournaments are on a surface. Do not use for a player's career profile: see player-scouting. Do not use for two-player matchups: see head-to-head-analysis. Do not use for moneylines: see odds-explorer."
metadata:
version: 1.0.0
author: AcesAPI
---
# Match Lookup
> **Default data tool:** AcesAPI (`acesapi-tool`).
> Use `get_matches` (2 credits) for results by date, tournament or season, `get_schedule` (2) for upcoming matches, `get_tournament` (2) for a full draw, `list_tournaments` (1) for ids, `get_match` (2) for one match in detail.
You find tennis matches and read them correctly. Tennis has no teams, so every lookup is by date, tournament, round or player. Your job is to resolve the right id cheaply, pull the smallest result that answers the question, and report scores the way a tennis fan would read them.
## When to Use
- "Who won the Wimbledon final?" "What was the score?"
- "Who plays today?" "What is on the schedule this week?"
- "Show me the 2025 US Open draw" or "every final this season"
- "Which tournaments are on clay this spring, and who won them?"
- "Did the match go five sets?" "Were there tiebreaks?"
## When NOT to Use
- A player's career, ratings or form: see `player-scouting`
- Two players against each other: see `head-to-head-analysis`
- Lines and prices for a match: see `odds-explorer`
- Building a full pre-match brief: see `match-preview`
## Commands Available
| Command | What It Does | Credits |
|---------|-------------|---------|
| `list_tournaments` | A season's tournaments: dates, city, surface, Grand Slam flag, status, champion | 1 |
| `get_tournament` | Every singles match of one edition, grouped by round, with sets and seeds | 2 |
| `get_matches` | Matches by `date`, `tournament_id` or `season`; filter `round`, `status` | 2 |
| `get_schedule` | Scheduled and in-progress matches for the next `days` (default 7, max 30) | 2 |
| `get_match` | One match: sets, tiebreak points, ratings before and after, stats, odds | 2 |
| `search_players` | Resolve a name to a `player_id` when the question names a player | 1 |
## Commands That Do NOT Exist
| Not Available | Use Instead |
|--------------|-------------|
| `get_live_scores` | `get_schedule`, or `get_matches` with `status: "live"`; scores are set by set, not point by point |
| `search_matches` | `get_matches` with filters, or `get_player_matches` for one player |
| `get_draw` | `get_tournament` |
| `get_team_schedule` | There are no teams: use `get_schedule` or `get_player_matches` |
## How It Works
**Step 1: Resolve the target.**
| User says | Resolve with |
|-----------|--------------|
| A date ("yesterday", "last Sunday") | Convert to `YYYY-MM-DD`. Dates are UTC, so a late match in the Americas can land on the next date |
| A tournament name | `list_tournaments` for the season, match on `name`, take `tournament_id` (format `188-2025`) |
| A player name | `search_players`, then `get_player_matches` (5 credits) if their log is needed |
| "This week" | `get_schedule` with `days: 7` for upcoming, `get_matches` with `date` for each past day |
| WTA | Add `"league": "wta"` to every call |
**Step 2: Pull the narrowest result.**
- One day of results: `get_matches {"date": "2025-07-13"}`
- One tournament's final: `get_matches {"tournament_id": "188-2025", "round": "Final"}`
- All finals of a season: `get_matches {"season": 2025, "round": "Final", "limit": 100}`
- The whole draw: `get_tournament {"tournament_id": "188-2025"}` (`draw: "all"` adds qualifying, available from 2022)
**Step 3: Read the score.**
`score` is a string such as `4-6 6-4 6-4 6-4` or `7-6(5) 6-3`. In a set decided by a tiebreak the number in Preview of the first 4,000 characters. Read full instructions →
match-preview6.85 KB
---
name: match-preview
description: "One-page pre-match brief for an upcoming ATP or WTA match: tournament and surface context, both players' ratings overall and on the surface, recent form, head to head, route through the draw, and the market price if the event is covered. Use when the user asks for a preview, a matchup breakdown, or who is favored in a specific match. Do not use for results: see match-lookup. Do not use for deep single-player work: see player-scouting."
metadata:
version: 1.0.0
author: AcesAPI
---
# Match Preview
> **Default data tool:** AcesAPI (`acesapi-tool`).
> A full preview costs about 20 credits: `get_schedule` (2) or `get_matches` (2) to find the match, `get_player` (5) for each player, `get_head_to_head` (3), `get_match` (2) for the odds. Add `get_surface_splits` (3 each) when the surface matters.
You assemble a decision-ready brief. Lead with who the evidence favors and by how much, then the reasons. Keep every number sourced from a call you made. Say what is missing: there are no serve statistics, injury reports or court-speed data.
## When to Use
- "Preview tomorrow's Alcaraz match"
- "Who is favored in the Shanghai third round, and why?"
- "Break down the final for me"
## When NOT to Use
- A finished match: see `match-lookup`
- A single player's whole profile: see `player-scouting`
## Commands Available
| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_schedule` | Find the match, `match_id`, start time, round, court | 2 |
| `get_match` | Match record, ratings (null before play), and the `odds` array | 2 |
| `get_player` | Bio, ratings by surface, career record, last 10 | 5 |
| `get_head_to_head` | Meetings, by surface, in finals | 3 |
| `get_surface_splits` | Record on today's surface | 3 |
| `get_player_stats` | Record against top-10 and top-50 opponents | 3 |
| `get_tournament` | The draw: who each player has beaten to get here | 2 |
| `get_rankings` | Ranking and points, if the question is about seeding | 2 |
| `search_players` | Ids from names | 1 |
## Commands That Do NOT Exist
| Not Available | Use Instead |
|--------------|-------------|
| `get_preview` | This skill builds it from the calls above |
| `get_odds` | `get_match` and read `odds` |
| `get_serve_stats` | Not available |
| `get_court_speed` | Not available: use surface and the `indoor` flag only |
| `get_injury_report` | Not available: say so, and look at recent retirements in `get_player_matches` |
## How It Works
**Step 1: Find the match.** `get_schedule {"days": 2}`. Real fields: `match_id`, `tournament`, `surface`, `indoor`, `round`, `court`, `starts_at`, `player1`, `player2`, `seed1`, `seed2`, `status`. `indoor` can be `null` when unknown; do not assume.
**Step 2: Pull the match record and price.** `get_match {"match_id": "..."}`. Before play, `players[].rating` is `null` (real response for a scheduled match), so take ratings from `get_player` or `get_ratings`. `odds` has one row per book if the event is a Grand Slam, a Masters or a WTA 1000; otherwise it is empty.
**Step 3: Ratings and form.** `get_player` for each player: overall rating, surface rating and match count on today's surface, career record, last 10. Compute the rating change over the last 10 completed matches as the form signal (see `player-scouting`).
**Step 4: Head to head.** `get_head_to_head`. Weigh by sample size (see `head-to-head-analysis`).
**Step 5: Route.** `get_tournament` for the event: list each player's wins so far, and total games or sets dropped. A player who has played three three-setters carries more load than one who has won in straight sets. This is a signal from scores, not from physical data, so say "load proxy".
**Step 6: Model probability.** Elo probability from `surface-elo`. If the event is covered, add the no-vig close from `odds-analysis`. Do not force them to agree; show both.
**Step 7: Write the brief.**
## Worked Example (structure with real fields)
Match: Alexander Zverev vsPreview of the first 4,000 characters. Read full instructions →
odds-analysis6.97 KB
--- name: odds-analysis description: "Odds math for two-way tennis markets: American to implied probability, margin, devigging (multiplicative, power, Shin), no-vig fair odds, consensus across books, favorite-longshot bias, and expected value of a price. Use when the user asks what a price implies, how to remove the vig, what the true probability is, or how to compare books. Do not use for fetching or comparing live prices: see odds-explorer." metadata: version: 1.0.0 author: AcesAPI --- # Odds Analysis > **Default data tool:** AcesAPI (`acesapi-tool`). > Prices come from the `odds` array of `get_match` (2 credits per match). The math here needs no calls. With the user's own prices, it costs nothing. You are an expert in converting prices to probabilities. The market's close is the best public estimate of a match's win probability, so most of this skill is about reading it correctly: remove the margin without distorting the favorite and the underdog. ## When to Use - "What probability does -450 imply?" - "Remove the vig from this line" - "What is the fair price if the book charges 4 percent?" - "Which book is sharpest?" (lowest margin, closest to consensus) - "Why does the underdog look like value after I devig?" (usually a method problem) ## When NOT to Use - Fetching and displaying prices: see `odds-explorer` ## Commands Available | Command | What It Does | Credits | |---------|-------------|---------| | `get_match` | Source of `odds`: `p1_open`, `p1_close`, `p2_open`, `p2_close` per book | 2 | | `get_schedule` | Find `match_id` for upcoming matches | 2 | | `get_matches` | Find `match_id` for past matches | 2 | ## Commands That Do NOT Exist | Not Available | Use Instead | |--------------|-------------| | `get_odds` | `get_match` | | `get_implied_probability` | Compute from the formulas below | | `get_consensus_line` | Average no-vig probabilities across the rows of `odds` | ## Conversion ``` American a > 0: decimal = 1 + a/100; implied = 100/(a+100) American a < 0: decimal = 1 + 100/(-a); implied = (-a)/(-a+100) fair American from probability p: p >= 0.5 -> -100p/(1-p); p < 0.5 -> 100(1-p)/p margin (overround) = implied1 + implied2 - 1 ``` A two-way tennis market has exactly two outcomes (no draw), which makes devigging a one-parameter problem. ## Devigging Methods Let `q1`, `q2` be the raw implied probabilities with `q1 + q2 = 1 + m`. **1. Multiplicative (proportional).** `p_i = q_i / (q1 + q2)`. Fast, standard. It removes margin in proportion to price, which tends to leave the favorite slightly underpriced and the underdog overpriced when books load margin on longshots. **2. Additive.** `p_i = q_i - m/2`. Equal subtraction per side. Can push a longshot's probability below zero on lopsided matches. Do not use when either price is beyond about +1000. **3. Power.** Find `k` such that `q1^k + q2^k = 1`, then `p_i = q_i^k`. Shrinks longshots more than favorites, which matches the favorite-longshot bias seen in most markets. Solve with bisection on `k` in [1, 2]. **4. Shin.** Models the margin as protection against informed bettors. Solve numerically for `z` (the insider proportion): ``` with S = q1 + q2: p_i(z) = (sqrt(z^2 + 4(1-z) q_i^2 / S) - z) / (2(1-z)) find z in [0, 0.4] such that p_1(z) + p_2(z) = 1 (bisection) ``` Treat Shin and power as the better choices for lopsided matches and multiplicative for near-even matches. When methods differ by less than 0.5 percentage points, the choice does not matter. ## Worked Example (real data: Pinnacle, 2025 Wimbledon final, close) `p1_close = -116`, `p2_close = 105`. ``` q1 = 116/216 = 0.5370 q2 = 100/205 = 0.4878 margin = 2.48% multiplicative: p1 = 0.5370 / 1.0248 = 0.5240 fair American for p1: -100*0.5240/0.4760 = -110 ``` So the market's fair price on Sinner was about -110 against -116 on offer. Lopsided case (real data: Pinnacle close, 2026 Shanghai Masters third round, scheduled at time of the call): `-673` and `+537`. ``` q1 =
Preview of the first 4,000 characters. Read full instructions →
odds-explorer7.02 KB
---
name: odds-explorer
description: "Multi-book tennis moneyline comparison, best price, margin (vig) per book, and open-to-close line movement for Grand Slam, ATP Masters and WTA 1000 matches. Use when the user asks about the odds on a match, which book has the best price, how a line moved, or what the market thinks. Do not use for devigging math or true probabilities: see odds-analysis. Do not use for results: see match-lookup."
metadata:
version: 1.0.0
author: AcesAPI
---
# Odds Explorer
> **Default data tool:** AcesAPI (`acesapi-tool`).
> There is no odds tool. Moneylines live in the `odds` array of `get_match` (2 credits per match). Get the `match_id` first from `get_schedule` (2) or `get_matches` (2).
You are an expert in reading a two-way tennis market. Your goal is to help the user see the best available price, how much margin each book charges, and how the market moved from open to close.
## Coverage, Stated Plainly
- Grand Slam singles matches from mid-2020, and ATP Masters and WTA 1000 matches from August 2024. Other tournaments return an empty `odds` array.
- Pre-match only: one open price and one close price per book. No in-play prices, no props, no set betting.
- Source: The Odds API, refreshed hourly. Books seen in live responses: 888sport, Betfair Exchange, DraftKings, FanDuel, Pinnacle, William Hill.
- Prices are American odds. `p1_*` is the player in the `player1` position of the match record, not the favorite.
## When to Use
- "What are the odds on tomorrow's Masters match?"
- "Which book has the best price on the underdog?"
- "How much did the line move before the final?"
- "What does Pinnacle think compared with DraftKings?"
## When NOT to Use
- Removing the margin or computing fair probabilities: see `odds-analysis`
## Commands Available
| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_match` | One match with its `odds` array: per book `p1_open`, `p1_close`, `p2_open`, `p2_close`, `games_spread1`, `games_total` | 2 |
| `get_schedule` | Upcoming matches with `match_id`, to find what is priced | 2 |
| `get_matches` | Past matches with `match_id`, for historical lines | 2 |
| `search_players` | Resolve a name before searching the schedule | 1 |
## Commands That Do NOT Exist
| Not Available | Use Instead |
|--------------|-------------|
| `get_odds` | `get_match` and read `odds` |
| `get_line_movement` | Compare `p1_open` with `p1_close` from `get_match`; only two points exist per book |
| `get_live_odds` | Not available: pre-match only |
| `get_player_props` | Not available |
| `get_set_betting` | Not available |
## Fields in the odds Array
| Field | Meaning |
|-------|---------|
| `book` | Sportsbook name |
| `p1_open`, `p2_open` | American odds at the first snapshot |
| `p1_close`, `p2_close` | American odds at the last pre-match snapshot |
| `games_spread1` | Handicap in games for player 1, present for a few books only (Pinnacle in the examples), else `null` |
| `games_total` | Total games line, present for a few books only, else `null` |
For a match that has not started, "close" is the latest price, not yet final.
## How It Works
**Step 1: Find the match.** `get_schedule {"days": 2}` lists matches with `match_id`. If the user names players, filter by `player1` and `player2`. Check the tournament is a Slam, Masters or WTA 1000 before spending credits on odds.
**Step 2: Pull odds.** `get_match {"match_id": "184930"}`. Read `odds`.
**Step 3: Price table.** For each book show both players' close price, the margin, and the no-vig probability for player 1.
```
implied(a) = 100 / (a + 100) if a > 0
= -a / (-a + 100) if a < 0
margin = implied(p1) + implied(p2) - 1
no_vig_p1 = implied(p1) / (implied(p1) + implied(p2))
```
**Step 4: Best price.** Per side, the best price is the highest number (+250 beats +230, -110 beats -120). In tennis the sides are far from symmetric, so the best price for the favorite and for the underdog are uPreview of the first 4,000 characters. Read full instructions →
player-scouting8.61 KB
---
name: player-scouting
description: "Scouting and comparing ATP and WTA players: profile, ranking, Elo overall and by surface, career record, titles, form, record by surface, round and opponent ranking, and ranking trajectory. Use when the user asks about a player, who is better on a surface, recent form, how a player does against top-10 opponents, or to compare two players side by side. Do not use for a single match result: see match-lookup. Do not use for two players' direct meetings: see head-to-head-analysis. Do not use for win probabilities: see surface-elo."
metadata:
version: 1.0.0
author: AcesAPI
---
# Player Scouting
> **Default data tool:** AcesAPI (`acesapi-tool`).
> Start with `search_players` (1 credit) for the id. `get_player` (5) is the one-call profile. Add `get_surface_splits` (3), `get_player_stats` (3) and `get_rankings` (2, with `player_id`) for depth. `get_player_matches` (5 per page) is the match log.
You build scouting reports from results data. Be honest about what the data is: scores, surfaces, rankings and Elo. It has no serve statistics, no injuries and no fitness data, so a report on "serve strength" is a proxy report and must say so.
## When to Use
- "Tell me about Jannik Sinner" or "profile Iga Swiatek"
- "Who is better on clay, A or B?" (see also `head-to-head-analysis`)
- "How does she do against top-10 players?"
- "Is he in form?" "Has the ranking moved?"
- "Compare these two players across everything"
## When NOT to Use
- One match's score or schedule: see `match-lookup`
- Two players' direct meetings only: see `head-to-head-analysis`
- A win probability for a specific match: see `surface-elo`
- Betting prices: see `odds-explorer`
## Commands Available
| Command | What It Does | Credits |
|---------|-------------|---------|
| `search_players` | Find ids by `name` and or `country` (3-letter code); most-played first | 1 |
| `get_player` | Bio, latest ranking, overall and per-surface rating with peak, career record, titles, Grand Slam titles, last 10 matches | 5 |
| `get_surface_splits` | Record by surface: wins, losses, win rate, sets, share of games won, tiebreaks, deciding sets, 6-0 sets; `season`, `by_season` | 3 |
| `get_player_stats` | Season stats counted from scores; record by round and by opponent ranking (top 10, 11-50, 51-100) | 3 |
| `get_rankings` | With `player_id`: ranking history with `previous_rank` and points | 2 |
| `get_player_matches` | Match log newest first with `rating_before` and `rating_after`; filters `season`, `surface`, `opponent_id`, `tournament_id`, `draw` | 5 |
| `get_ratings` | Elo leaderboard, to place the player among peers | 2 |
## Commands That Do NOT Exist
| Not Available | Use Instead |
|--------------|-------------|
| `get_serve_stats` | Not available. Use proxies from `get_surface_splits` (tiebreaks, share of games won) and say they are proxies |
| `get_player_injuries` | Not available |
| `get_player_form` | Build it from `get_player_matches` (see below) |
| `compare_players` | Run the calls for each player and tabulate |
## How It Works
**Step 1: Resolve the id.** `search_players {"name": "alcaraz"}`. Real output includes `player_id`, `country`, `date_of_birth`, `matches`, `last_match`, `current_rank` (null outside the top 100 to 150), `plays`. If two players match, ask or use `country`. For the WTA add `"league": "wta"`.
**Step 2: Profile.** `get_player {"player_id": "3782"}` for bio, rating and last 10 matches. This is the single best 5-credit call.
**Step 3: Surface fit.** `get_surface_splits` returns, per surface, `win_pct`, `games_won_pct`, `tiebreaks_won`, `tiebreaks_lost`, `deciding_sets_won`, `sets_won_6_0`, and a `ratings` block with a rating and match count per surface. Real example, trimmed (Alcaraz, career at the time of the call):
```json
{"overall": {"wins": 307, "losses": 69, "win_pct": 0.816, "games_won_pct": 0.58},
"splits": [
{"surface": "hard", "wins": 168, "losses": 46, "win_pct": 0.785, "games_won_pct": 0.5Preview of the first 4,000 characters. Read full instructions →
probability-calibration7.08 KB
--- name: probability-calibration description: "Checking and fixing tennis win probabilities: reliability tables, calibration slope and intercept, Brier decomposition, Platt scaling, isotonic regression, temperature scaling, and calibrating against the market close. Use when the user asks whether a model's 70 percent means 70 percent, why a model is overconfident on favorites, or how to correct probabilities. Do not use for training the model: see feature-engineering. Do not use for choosing validation splits: see walk-forward-validation." metadata: version: 1.0.0 author: AcesAPI --- # Probability Calibration > **Default data tool:** AcesAPI (`acesapi-tool`). > Out-of-sample predictions with outcomes are the input. Results and `winner_id` come from `get_matches` (2 credits per 100). Market closes for comparison come from `get_match` (2 each). You test whether predicted probabilities match reality. In tennis this is where models usually fail: a rating gap turns into a probability through a formula with assumptions (a fixed 400-point scale, one format, constant surface effect) and those assumptions break at the extremes and in best-of-five. ## When to Use - "When my model says 75 percent, how often does the player win?" - "My Elo probabilities seem too extreme for heavy favorites" - "Should I calibrate?" - "Is the market better calibrated than my model?" ## When NOT to Use - Building features or fitting models: see `feature-engineering` - Deciding how to split data: see `walk-forward-validation` ## Commands Available | Command | What It Does | Credits | |---------|-------------|---------| | `get_matches` | Outcomes (`winner_id`) and format context for scoring predictions | 2 | | `get_match` | Pre-match ratings and market closes for comparison | 2 | | `get_ratings` | Current ratings for forward predictions | 2 | | `get_player_matches` | `rating_before` for many matches of one player | 5 | ## Commands That Do NOT Exist | Not Available | Use Instead | |--------------|-------------| | `get_calibration` | Compute from your prediction log | | `get_model_probability` | Your model; the API serves data only | | `get_historical_predictions` | Not available | ## Step 1: Orient the Predictions Calibration needs one probability per match for one named side and a 0 or 1 label. Make it unambiguous: `p` = probability that the focal player wins, `y` = 1 if `winner_id` equals the focal player's id. If you keep both orientations of each match, score one orientation, or the table duplicates every row and halves your apparent uncertainty. ## Step 2: Reliability Table Bin predictions, then compare predicted and realized. ``` bins: [0.0, 0.1) ... [0.9, 1.0] # use equal-count bins when n is small for each bin: n, mean(p), mean(y), mean(y) - mean(p) ``` For tennis, put extra bins above 0.8, because heavy favorites are common (the best players are favored at 85 to 95 percent in early rounds). A model that says 92 percent and wins 88 percent costs real money when a price is -1000. Report `n` per bin. A bin with 12 matches has a standard error near 0.14 for a 50 percent rate. Show intervals (Wilson) for bins under 100. ## Step 3: Summary Numbers | Measure | Definition | Reading | |---------|------------|---------| | Brier | `mean((p - y)^2)` | 0.25 for a coin flip | | Log loss | `-mean(y ln p + (1-y) ln(1-p))` | 0.693 for a coin flip | | Calibration intercept and slope | Fit `logit(P(y=1)) = a + b * logit(p)` | Perfect: a = 0, b = 1. b < 1 means overconfident; b > 1 means underconfident | | Expected calibration error | `sum(n_k / n * abs(mean y_k - mean p_k))` | Sensitive to binning; report with the bin scheme | Slope is the clearest single number. Elo-based tennis probabilities tend to show a slope below 1 in best-of-three matches among close players and above 1 in best-of-five, because best of five amplifies the better player's edge. Test it on your data; do not assume. ## Step 4: Fix It Fit the calibrator on a validatio
Preview of the first 4,000 characters. Read full instructions →
ranking-history6.72 KB
---
name: ranking-history
description: "ATP and WTA ranking lists and trajectories: the current top list, the list in force on a past date, a player's ranking history, points movement, ranking versus Elo, and seeding context. Use when the user asks who is number one, how a ranking changed, what the ranking was on a date, or whether the ranking understates a player. Do not use for strength estimates: see surface-elo. Do not use for a full player profile: see player-scouting."
metadata:
version: 1.0.0
author: AcesAPI
---
# Ranking History
> **Default data tool:** AcesAPI (`acesapi-tool`).
> `get_rankings` (2 credits) returns a weekly list or, with `player_id`, one player's history. `get_ratings` (2) is the Elo leaderboard for comparison.
You answer ranking questions precisely and explain what a ranking is. A ranking is a rolling points total, not a strength estimate, so use it for seeding and bracket questions and use ratings for strength.
## Coverage
ESPN's weekly ATP and WTA lists since 2007, top 100 to 150 a week. The data page counts 1,637 weekly lists covered from 2007 to 2026. Players outside the top 100 to 150 are not in a given week. Some calendar weeks have no list (a real history skipped weeks 33 and 36 in 2026).
## When to Use
- "Who is number one?" "Show me the top 10."
- "What was the ranking list on 1 January 2020?"
- "How did Alcaraz's ranking move this season?"
- "Is the number two ranked player really the second best?"
## When NOT to Use
- Win probability or strength: see `surface-elo`
- A scouting report: see `player-scouting`
- Results: see `match-lookup`
## Commands Available
| Command | What It Does | Credits |
|---------|-------------|---------|
| `get_rankings` | Latest list (default), or by `season` and `week`, or the list in force on `date`; with `player_id`, that player's history and `best_rank`; `limit` caps list rows | 2 |
| `get_ratings` | Elo leaderboard, overall or by surface | 2 |
| `search_players` | Ids, and `current_rank` (null outside the list) | 1 |
| `get_player` | `ranking` block with latest rank and points, plus ratings | 5 |
## Commands That Do NOT Exist
| Not Available | Use Instead |
|--------------|-------------|
| `get_standings` | `get_rankings` |
| `get_race_rankings` | Not available: only the official weekly list |
| `get_ranking_points_breakdown` | Not available: only total points per week |
| `get_live_rankings` | Not available: lists are weekly |
## How It Works
**Step 1: Pick the query.**
| Question | Call |
|----------|------|
| Current top N | `get_rankings {"limit": 10}` |
| List on a past date | `get_rankings {"date": "2020-01-06"}` |
| A specific week | `get_rankings {"season": 2025, "week": 20}` |
| One player's path | `get_rankings {"player_id": "3623"}` |
| WTA | add `"league": "wta"` |
**Step 2: Read the shape.** A list row: `rank`, `previous_rank`, `points`, `player_id`, `name`, `country`; the response has `season`, `week` and `ranking_date`. The latest list at the time of the call was week 40 of 2026, dated 2026-10-01, with the top three as Sinner (11,000 points), Zverev (9,720) and Alcaraz (5,560). A history row: `season`, `week`, `ranking_date`, `rank`, `previous_rank`, `points`, newest first.
**Step 3: Compute what the user wants.** Peak, time at number one, net change over a window, biggest weekly drop. Always show the date range.
## Points Versus Rank
Points tell the story that rank hides. In the real history above, the world number one's points fell from 13,450 (week 31) to 11,000 (week 40) with rank 1 held throughout. That is a drop of 2,450 points, almost 18 percent, while the headline did not move. Read `points` for momentum and the gap to the next player for how safe the position is: 11,000 against 9,720 is a 1,280-point cushion.
Points move for two reasons: new results added, and last year's results dropped off after 52 weeks. A fall is therefore not always bad form. Compare with the player's results from the same weeks a year earlPreview of the first 4,000 characters. Read full instructions →
set-score-modeling9.29 KB
--- name: set-score-modeling description: "Modeling tennis scores without serve data: match win probability from set and game win probabilities, best-of-three versus best-of-five, set-score distributions (2-0, 2-1, 3-0), tiebreak rates, total games and games handicap lines, and the games_total and games_spread1 fields in match odds. Use when the user asks about straight sets odds, how many games a match will run, tiebreak frequency, or how a match probability maps to set scores. Do not use for rating-based win probability: see surface-elo. Do not use for player props: not supported." metadata: version: 1.0.0 author: AcesAPI --- # Set Score Modeling > **Default data tool:** AcesAPI (`acesapi-tool`). > `get_matches` (2 credits per 100 matches) gives every set and tiebreak score for building distributions. `get_surface_splits` (3) and `get_player_stats` (3) give tiebreak and games-won rates. `get_match` (2) has the `games_total` and `games_spread1` lines inside `odds` for the few books that post them. You model the shape of a match: how likely a straight-sets win is, how many games to expect, how often tiebreaks occur. The standard tool for this, a point-by-point serve and return model, needs first-serve and break-point data that this dataset does not contain. So this skill builds from what scores give: games won share, hold-rate proxies and empirical set distributions. Say that out loud when presenting results. ## When to Use - "How likely is a straight-sets win?" - "What is a reasonable total games line for this match?" - "How often do ATP matches on grass go to a tiebreak?" - "Convert a 70 percent match probability into set probabilities" - "Is the market's games handicap consistent with its moneyline?" ## When NOT to Use - A pure win probability from ratings: see `surface-elo` - Serve speed, aces, break points, player props: not in the data ## Commands Available | Command | What It Does | Credits | |---------|-------------|---------| | `get_matches` | Matches with `sets[]` (`games1`, `games2`, `tiebreak1`, `tiebreak2`, `winner`) for building distributions | 2 | | `get_match` | `stats.total_games`, `stats.tiebreaks`, `stats.went_the_distance`; `odds[].games_total`, `odds[].games_spread1` | 2 | | `get_surface_splits` | Per-surface sets, games, tiebreaks, deciding sets, 6-0 sets | 3 | | `get_player_stats` | Season record with tiebreaks and deciding sets, by round | 3 | | `get_head_to_head` | Scores of prior meetings | 3 | | `get_tournament` | All scores of an event (for event-level game totals) | 2 | ## Commands That Do NOT Exist | Not Available | Use Instead | |--------------|-------------| | `get_serve_stats` | Not available. ESPN publishes no serve statistics; `get_match` returns `serve_stats: null` | | `get_break_points` | Not available | | `get_point_by_point` | Not available | | `get_set_odds` | Not available: only moneylines plus `games_total` and `games_spread1` on some books | | `get_total_games_line` | Read `games_total` from `get_match` | ## From Match Probability to Set Probability Let `q` be a player's probability of winning any one set (assumed constant and independent, which is an approximation). Then: ``` Best of three: P(match) = q^2 (3 - 2q) Best of five: P(match) = q^3 (10 - 15q + 6q^2) ``` To go from a match probability `P` (from `surface-elo` or a no-vig close) back to `q`, solve by bisection. Real example: a no-vig match probability of 0.524 (Pinnacle close for Sinner in the 2025 Wimbledon final, best of five) corresponds to `q = 0.5128`; in best of three the same match probability needs `q = 0.516`. The format matters: the same set strength gives a more extreme match probability in best of five. **Set-score distribution, best of three, for player A with set probability q:** ``` P(A wins 2-0) = q^2 P(A wins 2-1) = 2 q^2 (1 - q) P(B wins 2-1) = 2 (1-q)^2 q P(B wins 2-0) = (1-q)^2 ``` At `q = 0.6`: 36 percent for 2-0, 28.8 percent for 2-1, and 64.8 percent match win. These are model o
Preview of the first 4,000 characters. Read full instructions →
surface-elo8.17 KB
--- name: surface-elo description: "Tennis Elo by surface: how AcesAPI ratings are built, reading overall versus hard, clay and grass ratings, converting a rating gap to a win probability, tracking rating change over time, and building your own surface-weighted blend. Use when the user asks who is best on clay, how likely A is to beat B on grass, how a rating moved, or how to build a surface rating. Do not use for direct meetings: see head-to-head-analysis. Do not use for set or game-count markets: see set-score-modeling." metadata: version: 1.0.0 author: AcesAPI --- # Surface Elo > **Default data tool:** AcesAPI (`acesapi-tool`). > `get_ratings` (2 credits) is the leaderboard overall or by surface. `get_player` (5) gives one player's overall and surface ratings with peak. `get_match` (2) gives both players' ratings before and after a match. `get_player_matches` (5 per page) gives `rating_before` and `rating_after` over time. You turn ratings into probabilities. The ratings are AcesAPI's own, computed from every completed tour-level singles match since 2007, so they are inspectable and you can check any claim against `get_match`. ## How the Rating Is Built From the `method` field returned by `get_ratings`: - Elo from every completed singles match on the tour since 2007 (main draw and qualifying), start 1500 - `K = 250 / (matches + 5)^0.4`, so new players move fast and veterans move slowly - The surface rating counts only matches on that surface, so it has its own match count and its own K - Walkovers, defaults and retirements are not rated - A player is listed once they have at least 10 rated matches and a rated match since `active_since` (default one year ago) **Consistency check on the 2025 Wimbledon final** (real `get_match` output): Sinner overall 2325 to 2335 (+10), Alcaraz 2278 to 2267 (-11). With the standard 400-point logistic, Sinner's expected score was 0.567, so a +10 change implies K of about 23. The formula gives about 23 for a player with roughly 400 rated matches. The overall rating is therefore consistent with a 400-point logistic scale. ## When to Use - "Who is the best clay-court player right now?" - "What is Sinner's chance against Alcaraz on grass?" - "How much did that loss cost him?" - "Is a player's grass rating real or based on 12 matches?" - "Build a better rating than the overall one" ## When NOT to Use - The direct record between the two: see `head-to-head-analysis` - Total games, set scores, tiebreak odds: see `set-score-modeling` ## Commands Available | Command | What It Does | Credits | |---------|-------------|---------| | `get_ratings` | Leaderboard: `rank`, `rating`, `surface_matches`, `last_match`; filter `surface`, `active_since`, `limit` | 2 | | `get_player` | One player: overall rating, per-surface ratings, peak | 5 | | `get_match` | `players[].rating`: `before`, `after`, `surface_before`, `surface_after` | 2 | | `get_player_matches` | `rating_before`, `rating_after` per match, filter by `surface` | 5 | | `get_surface_splits` | `ratings` block with a rating and match count per surface | 3 | ## Commands That Do NOT Exist | Not Available | Use Instead | |--------------|-------------| | `get_elo` | `get_ratings` | | `get_win_probability` | Compute from the formula below | | `get_rating_history` | `get_player_matches` and read `rating_after` in date order | | `get_serve_rating` | Not available: no serve data | ## Win Probability For ratings `Ra` and `Rb` on the same scale: ``` P(A beats B) = 1 / (1 + 10^(-(Ra - Rb) / 400)) ``` This is a single-match probability for the whole match, regardless of format. A 100-point gap is 64 percent, 200 is 76 percent, 300 is 85 percent. **Worked example (real ratings before the 2025 Wimbledon final, grass):** Sinner surface 1995, Alcaraz surface 2176. Gap -181, so P(Sinner) = 1 / (1 + 10^(0.4525)) = 0.26. The overall ratings (2325 against 2278) said 0.57. The two numbers disagree by 31 points of probability because the grass ratings had
Preview of the first 4,000 characters. Read full instructions →
walk-forward-validation7.97 KB
--- name: walk-forward-validation description: "Time-ordered validation of tennis models: expanding or rolling windows, season-by-season walk-forward, purging, log loss, Brier score and accuracy, baselines (Elo, ranking, market close), and significance tests. Use when the user asks if a model is any good, how to split data, whether cross-validation is okay, or how to compare two models. Refuses random k-fold on matches. Do not use for building features: see feature-engineering. Do not use for probability reliability: see probability-calibration." metadata: version: 1.0.0 author: AcesAPI --- # Walk-Forward Validation > **Default data tool:** AcesAPI (`acesapi-tool`). > Results come from `get_matches` (2 credits per 100 matches). Use `get_ratings` or `get_player_matches` (`rating_before`) for baseline ratings. For the market baseline, `get_match` (2 each) on Slam and Masters matches. You make sure a tennis model is judged only on matches that happened after the data it learned from. Tennis models fail in a specific way: a player's rating, form and context drift week to week, so shuffled validation looks excellent and forecasts look average. ## When to Use - "Is my model any good?" - "Can I use k-fold cross-validation?" (no) - "How do I split a multi-season dataset?" - "Is model B really better than model A?" ## When NOT to Use - Constructing features: see `feature-engineering` - Judging probability calibration alone: see `probability-calibration` ## Commands Available | Command | What It Does | Credits | |---------|-------------|---------| | `get_matches` | Source of labeled matches by season | 2 | | `get_player_matches` | `rating_before` for baselines | 5 | | `get_match` | Pre-match ratings and market close for a baseline | 2 | | `get_ratings` | Current leaderboard, only for current-season sanity checks | 2 | | `list_tournaments` | Season boundaries and tiers | 1 | ## Commands That Do NOT Exist | Not Available | Use Instead | |--------------|-------------| | `get_train_test_split` | Split by `match_date` yourself | | `get_model_score` | Compute locally | | `get_historical_predictions` | Not available: keep your own prediction log | ## The Rule Every prediction for a match on date `t` uses only information with timestamps before `t`: model weights trained on matches before `t` and features built from the past. No exceptions for "just a few features" or "the same tournament". ## Split Schemes **Season walk-forward (default).** Tour data runs by calendar year, so evaluate each season in turn: ``` train: 2007..2018 test: 2019 train: 2007..2019 test: 2020 train: 2007..2020 test: 2021 ... ``` Expanding window keeps all history. Rolling window (for example the last 8 seasons) adapts to change at the cost of data. Compare both; with Elo-style features, expanding usually wins. **Month or tournament-level folds** for more test points: refit monthly and predict the next month. Costly but gives a finer drift picture. **Purging.** Matches on the same day, and consecutive rounds of one tournament, share players and context. Leave a one-week gap between train and test when the model uses short-window form, so a player's recent matches do not appear on both sides. **Embargo for ratings.** If your rating is updated daily, evaluate with the rating as of the morning of the match, built from earlier dates only. ## Metrics | Metric | Formula | Notes | |--------|---------|-------| | Log loss | `-mean(y ln p + (1-y) ln(1-p))` | The primary score. Punishes confident errors | | Brier score | `mean((p - y)^2)` | Easier to read, 0.25 is a coin flip | | Accuracy | `mean((p > 0.5) == y)` | Context only; ignores confidence | | Calibration error | see `probability-calibration` | Separate check | Reference points to compute on your own data, not to quote as constants: a coin flip scores log loss 0.693 and Brier 0.25. Elo from `get_match` ratings is the baseline to beat. The market no-vig close (see `odds-analysis`) is the stro
Preview of the first 4,000 characters. Read full instructions →
Release notes
Initial release. Read-only tennis data tools with OAuth sign-in and 14 workflow skills.
Declared in the saved package. Remote tools may change independently.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package license
- MIT
- Package author
- AcesAPI
- Keywords
- See keywords
- Commerce declaration
- Does not support commerceThis does not establish whether access is free or paid.
- Review scenarios
- 5 positive · 3 negativeScenario counts, not test results.
Declared capabilities
- Look up ATP and WTA players and tournaments
- Read schedules, draws and match results
- Read rankings and surface Elo ratings
- Compare players head to head and by surface
- Read player match history and stats
Package observed Nov 6, 2026.
Technical details
- First seen
- Oct 31, 2026 · 14:53 UTC
- Last seen
- Nov 6, 2026 · 18:00 UTC
- Collection status
- Collected
plugin_asdk_app_6ad1467bd8f88191bead2eb0a9b87e49
Download plugin data (JSON)Plugin details: served from cache. Prepared . Refreshed when plugin data changes or after 6 hours. Your account and Follow status are live.