ATP API
Welcome to the BALLDONTLIE ATP API, your comprehensive source for ATP Tour data. This API covers men's singles tennis only. Data includes player profiles, tournament information, ATP rankings, match results, detailed statistics, and betting odds from the ATP Tour. An API key is required. You can obtain an API key by creating a free account on our website. Read the authentication section to learn how to use the API key.
Take a look at our other APIs.
Join us on discord.
AI-Powered Integration
Using the OpenAPI Specification with AI
Our complete OpenAPI specification allows AI assistants to automatically understand and interact with our API. Simply share the spec URL with your AI assistant and describe what you want to build—the AI will handle the technical implementation.
Getting Started with AI:
- Copy this URL:
https://www.balldontlie.io/openapi.yml - Share it with your preferred AI assistant (ChatGPT, Claude, Gemini, etc.)
- Tell the AI what you want to build (e.g., "Create a dashboard showing ATP rankings")
- The AI will read the OpenAPI spec and write the code for you
Example prompts to try:
- "Using the OpenAPI spec at https://www.balldontlie.io/openapi.yml, show me how to get Carlos Alcaraz's match history"
- "Read the BALLDONTLIE OpenAPI spec and create a Python script that fetches the current ATP rankings"
- "Help me understand the available ATP Tennis endpoints from this OpenAPI spec: https://www.balldontlie.io/openapi.yml"
This makes it incredibly easy for non-technical users, analysts, and researchers to leverage our tennis data without needing to learn programming from scratch.
Account Tiers
There are three different account tiers which provide you access to different types of data. Visit our website to create an account for free.
Paid tiers do not apply across sports. The tier you purchase for ATP will not automatically be applied to other sports. You can purchase the ALL-ACCESS ($299.99/mo) tier to get access to every endpoint for every sport.
Read the table below to see the breakdown.
| Endpoint | Free | ALL-STAR | GOAT |
|---|---|---|---|
| Players | Yes | Yes | Yes |
| Tournaments | Yes | Yes | Yes |
| Rankings | Yes | Yes | Yes |
| Matches | No | Yes | Yes |
| ATP Race | No | Yes | Yes |
| Match Statistics | No | No | Yes |
| Player Career Stats | No | No | Yes |
| Head to Head | No | No | Yes |
| Betting Odds | No | No | Yes |
The feature breakdown per tier is shown in the table below.
| Tier | Requests / Min | $USD / mo. |
|---|---|---|
| GOAT | 600 | 39.99 |
| ALL-STAR | 60 | 9.99 |
| Free | 5 | 0 |
Authentication
To authorize, use this code:
curl "api_endpoint_here" -H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/players', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/players',
headers={'Authorization': 'YOUR_API_KEY'}
)
if response.status_code == 200:
data = response.json()
print(data)
else:
print(f'Error: {response.status_code}')
Make sure to replace
YOUR_API_KEYwith your API key.
BALLDONTLIE uses API keys to allow access to the API. You can obtain an API key by creating a free account at our website
We expect the API key to be included in all API requests to the server in a header that looks like the following:
Authorization: YOUR_API_KEY
Pagination
This API uses cursor based pagination rather than limit/offset. Endpoints that support pagination will send back responses with a meta key that looks like what is displayed on the right.
{
"meta": {
"next_cursor": 90,
"per_page": 25
}
}
You can use per_page to specify the maximum number of results. It defaults to 25 and doesn't allow values larger than 100.
You can use next_cursor to get the next page of results. Specify it in the request parameters like this: ?cursor=NEXT_CURSOR.
Errors
The API uses the following error codes:
| Error Code | Meaning |
|---|---|
| 401 | Unauthorized - You either need an API key or your account tier does not have access to the endpoint. |
| 400 | Bad Request -- The request is invalid. The request parameters are probably incorrect. |
| 404 | Not Found -- The specified resource could not be found. |
| 406 | Not Acceptable -- You requested a format that isn't json. |
| 429 | Too Many Requests -- You're rate limited. |
| 500 | Internal Server Error -- We had a problem with our server. Try again later. |
| 503 | Service Unavailable -- We're temporarily offline for maintenance. Please try again later. |
Players
Get All Players
curl "https://api.balldontlie.io/atp/v1/players?search=Alcaraz" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/players?search=Alcaraz', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/players',
headers={'Authorization': 'YOUR_API_KEY'},
params={'search': 'Alcaraz'}
)
if response.status_code == 200:
players = response.json()['data']
for player in players:
print(f"{player['full_name']} - {player['country']}")
The above command returns JSON structured like this:
{
"data": [
{
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
}
],
"meta": {
"per_page": 25
}
}
This endpoint retrieves all ATP players with optional search and filtering.
HTTP Request
GET https://api.balldontlie.io/atp/v1/players
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| search | string | Search players by name |
| first_name | string | Filter by first name |
| last_name | string | Filter by last name |
| country | string | Filter by country name |
| country_code | string | Filter by country code (e.g., "ESP") |
| player_ids | array | Filter by specific player IDs |
Get Specific Player
curl "https://api.balldontlie.io/atp/v1/players/1" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/players/1', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
player_id = 1
response = requests.get(
f'https://api.balldontlie.io/atp/v1/players/{player_id}',
headers={'Authorization': 'YOUR_API_KEY'}
)
if response.status_code == 200:
player = response.json()['data']
print(f"{player['full_name']} - Turned Pro: {player['turned_pro']}")
The above command returns JSON structured like this:
{
"data": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
}
}
This endpoint retrieves a specific player.
HTTP Request
GET https://api.balldontlie.io/atp/v1/players/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the player |
Tournaments
Get All Tournaments
curl "https://api.balldontlie.io/atp/v1/tournaments?season=2025&category=Grand%20Slam" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/tournaments?season=2025&category=Grand%20Slam', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/tournaments',
headers={'Authorization': 'YOUR_API_KEY'},
params={'season': 2025, 'category': 'Grand Slam'}
)
if response.status_code == 200:
tournaments = response.json()['data']
for tournament in tournaments:
print(f"{tournament['name']} - {tournament['surface']} - {tournament['category']}")
The above command returns JSON structured like this:
{
"data": [
{
"id": 225,
"name": "Australian Open",
"location": "Melbourne",
"surface": "Hard",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-01-12",
"end_date": "2025-01-26",
"prize_money": 43250000,
"prize_currency": "A$",
"draw_size": 128
},
{
"id": 249,
"name": "Roland Garros",
"location": "Paris",
"surface": "Clay",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-05-25",
"end_date": "2025-06-08",
"prize_money": 26334000,
"prize_currency": "€",
"draw_size": 128
},
{
"id": 256,
"name": "Wimbledon",
"location": "London",
"surface": "Grass",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-06-30",
"end_date": "2025-07-13",
"prize_money": 24919000,
"prize_currency": "£",
"draw_size": 128
},
{
"id": 313,
"name": "US Open",
"location": "New York",
"surface": "Hard",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-08-24",
"end_date": "2025-09-07",
"prize_money": 40412800,
"prize_currency": "$",
"draw_size": 128
}
],
"meta": {
"per_page": 25
}
}
This endpoint retrieves all ATP tournaments with optional filtering.
HTTP Request
GET https://api.balldontlie.io/atp/v1/tournaments
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| tournament_ids | array | Filter by specific tournament IDs |
| season | integer | Filter by season year |
| surface | string | Filter by surface (Hard, Clay, Grass, Carpet) |
| category | string | Filter by category (Grand Slam, ATP 1000, etc.) |
Get Specific Tournament
curl "https://api.balldontlie.io/atp/v1/tournaments/313?season=2025" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/tournaments/313?season=2025', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
tournament_id = 313 # US Open
response = requests.get(
f'https://api.balldontlie.io/atp/v1/tournaments/{tournament_id}',
headers={'Authorization': 'YOUR_API_KEY'},
params={'season': 2025}
)
if response.status_code == 200:
tournament = response.json()['data']
print(f"{tournament['name']} - Prize Money: {tournament['prize_money']} {tournament['prize_currency']}")
The above command returns JSON structured like this:
{
"data": {
"id": 313,
"name": "US Open",
"location": "New York",
"surface": "Hard",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-08-24",
"end_date": "2025-09-07",
"prize_money": 40412800,
"prize_currency": "$",
"draw_size": 128
}
}
This endpoint retrieves a specific tournament.
HTTP Request
GET https://api.balldontlie.io/atp/v1/tournaments/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the tournament |
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| season | integer | Get tournament info for a specific year. Defaults to most recent if omitted |
Rankings
Get Rankings
curl "https://api.balldontlie.io/atp/v1/rankings" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/rankings', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/rankings',
headers={'Authorization': 'YOUR_API_KEY'}
)
if response.status_code == 200:
rankings = response.json()['data']
for ranking in rankings[:10]:
player = ranking['player']
print(f"#{ranking['rank']}: {player['full_name']} - {ranking['points']} pts")
The above command returns JSON structured like this:
{
"data": [
{
"id": 8605,
"player": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"rank": 1,
"points": 12050,
"movement": 0,
"ranking_date": "2026-12-01"
},
{
"id": 8606,
"player": {
"id": 2,
"first_name": "Jannik",
"last_name": "Sinner",
"full_name": "Jannik Sinner",
"country": "Italy",
"country_code": "ITA",
"birth_place": "Italy",
"age": 24,
"height_cm": 191,
"weight_kg": 77,
"plays": "Right-Handed",
"turned_pro": 2018
},
"rank": 2,
"points": 11500,
"movement": 0,
"ranking_date": "2026-12-01"
},
{
"id": 8607,
"player": {
"id": 3,
"first_name": "Alexander",
"last_name": "Zverev",
"full_name": "Alexander Zverev",
"country": "Germany",
"country_code": "GER",
"birth_place": "Germany",
"age": 28,
"height_cm": 198,
"weight_kg": 90,
"plays": "Right-Handed",
"turned_pro": 2013
},
"rank": 3,
"points": 5105,
"movement": 0,
"ranking_date": "2026-12-01"
}
],
"meta": {
"next_cursor": 8607,
"per_page": 25
}
}
This endpoint retrieves current ATP rankings.
HTTP Request
GET https://api.balldontlie.io/atp/v1/rankings
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| player_ids | array | Filter rankings for specific player IDs |
| date | string | Get rankings for a specific date (YYYY-MM-DD) |
Matches
Get All Matches
curl "https://api.balldontlie.io/atp/v1/matches?tournament_ids[]=313&season=2025&round=Finals" \
-H "Authorization: YOUR_API_KEY"
const params = new URLSearchParams();
params.append('tournament_ids[]', 313); // US Open
params.append('season', 2025);
params.append('round', 'Finals');
const response = await fetch(`https://api.balldontlie.io/atp/v1/matches?${params}`, {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/matches',
headers={'Authorization': 'YOUR_API_KEY'},
params={'tournament_ids[]': 313, 'season': 2025, 'round': 'Finals'} # US Open Final
)
if response.status_code == 200:
matches = response.json()['data']
for match in matches:
p1 = match['player1']['full_name']
p2 = match['player2']['full_name']
score = match['score'] or 'TBD'
print(f"{p1} vs {p2}: {score}")
The above command returns JSON structured like this:
{
"data": [
{
"id": 50800,
"tournament": {
"id": 313,
"name": "US Open",
"location": "New York",
"surface": "Hard",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-08-24",
"end_date": "2025-09-07",
"prize_money": 40412800,
"prize_currency": "$",
"draw_size": 128
},
"season": 2025,
"round": "Finals",
"player1": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"player2": {
"id": 2,
"first_name": "Jannik",
"last_name": "Sinner",
"full_name": "Jannik Sinner",
"country": "Italy",
"country_code": "ITA",
"birth_place": "Italy",
"age": 24,
"height_cm": 191,
"weight_kg": 77,
"plays": "Right-Handed",
"turned_pro": 2018
},
"winner": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"score": "6-2 3-6 6-1 6-4",
"duration": "02:42:00",
"number_of_sets": 4,
"match_status": "F",
"is_live": false
}
],
"meta": {
"per_page": 25
}
}
This endpoint retrieves all matches with optional filtering.
HTTP Request
GET https://api.balldontlie.io/atp/v1/matches
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| tournament_ids | array | Filter matches by tournament IDs |
| player_ids | array | Filter matches involving specific players |
| season | integer | Filter matches by season year |
| round | string | Filter by round (Finals, Semi-Finals, etc.) |
| is_live | boolean | Filter for live matches only |
Get Specific Match
curl "https://api.balldontlie.io/atp/v1/matches/50800" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/matches/50800', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
match_id = 50800 # 2025 US Open Final
response = requests.get(
f'https://api.balldontlie.io/atp/v1/matches/{match_id}',
headers={'Authorization': 'YOUR_API_KEY'}
)
if response.status_code == 200:
match = response.json()['data']
print(f"{match['player1']['full_name']} vs {match['player2']['full_name']}")
print(f"Score: {match['score']} | Duration: {match['duration']}")
The above command returns JSON structured like this:
{
"data": {
"id": 50800,
"tournament": {
"id": 313,
"name": "US Open",
"location": "New York",
"surface": "Hard",
"category": "Grand Slam",
"season": 2025,
"start_date": "2025-08-24",
"end_date": "2025-09-07",
"prize_money": 40412800,
"prize_currency": "$",
"draw_size": 128
},
"season": 2025,
"round": "Finals",
"player1": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"player2": {
"id": 2,
"first_name": "Jannik",
"last_name": "Sinner",
"full_name": "Jannik Sinner",
"country": "Italy",
"country_code": "ITA",
"birth_place": "Italy",
"age": 24,
"height_cm": 191,
"weight_kg": 77,
"plays": "Right-Handed",
"turned_pro": 2018
},
"winner": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"score": "6-2 3-6 6-1 6-4",
"duration": "02:42:00",
"number_of_sets": 4,
"match_status": "F",
"is_live": false
}
}
This endpoint retrieves a specific match.
HTTP Request
GET https://api.balldontlie.io/atp/v1/matches/{id}
URL Parameters
| Parameter | Description |
|---|---|
| id | The ID of the match |
ATP Race
Get ATP Race Standings
curl "https://api.balldontlie.io/atp/v1/atp_race" \
-H "Authorization: YOUR_API_KEY"
const response = await fetch('https://api.balldontlie.io/atp/v1/atp_race', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/atp_race',
headers={'Authorization': 'YOUR_API_KEY'}
)
if response.status_code == 200:
race = response.json()['data']
for entry in race[:10]:
player = entry['player']
qualified = "Qualified" if entry['is_qualified'] else ""
print(f"#{entry['rank']}: {player['full_name']} - {entry['points']} pts {qualified}")
The above command returns JSON structured like this:
{
"data": [
{
"id": 499949,
"player": {
"id": 83,
"first_name": "Hubert",
"last_name": "Hurkacz",
"full_name": "Hubert Hurkacz",
"country": "Poland",
"country_code": "POL",
"birth_place": "Poland",
"age": 28,
"height_cm": 196,
"weight_kg": 81,
"plays": "Right-Handed",
"turned_pro": 2015
},
"ranking_date": "2026-01-12",
"rank": 1,
"points": 265,
"movement": 0,
"is_qualified": false
},
{
"id": 499950,
"player": {
"id": 11,
"first_name": "Alexander",
"last_name": "Bublik",
"full_name": "Alexander Bublik",
"country": "Kazakhstan",
"country_code": "KAZ",
"birth_place": "Russia",
"age": 28,
"height_cm": 196,
"weight_kg": 82,
"plays": "Right-Handed",
"turned_pro": 2016
},
"ranking_date": "2026-01-12",
"rank": 2,
"points": 250,
"movement": 0,
"is_qualified": false
},
{
"id": 499951,
"player": {
"id": 13,
"first_name": "Daniil",
"last_name": "Medvedev",
"full_name": "Daniil Medvedev",
"country": "Russia",
"country_code": "RUS",
"birth_place": "Russia",
"age": 29,
"height_cm": 198,
"weight_kg": 83,
"plays": "Right-Handed",
"turned_pro": 2014
},
"ranking_date": "2026-01-12",
"rank": 2,
"points": 250,
"movement": 0,
"is_qualified": false
}
],
"meta": {
"next_cursor": 499951,
"per_page": 25
}
}
This endpoint retrieves the ATP Race to Turin standings. The ATP Race tracks points accumulated during the current calendar year, determining qualification for the season-ending ATP Finals.
HTTP Request
GET https://api.balldontlie.io/atp/v1/atp_race
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| player_ids | array | Filter for specific player IDs |
| date | string | Get standings for a specific date (YYYY-MM-DD) |
Match Statistics
Get Match Statistics
curl "https://api.balldontlie.io/atp/v1/match_stats?match_ids[]=50800" \
-H "Authorization: YOUR_API_KEY"
const params = new URLSearchParams();
params.append('match_ids[]', 50800); // 2025 US Open Final
const response = await fetch(`https://api.balldontlie.io/atp/v1/match_stats?${params}`, {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
response = requests.get(
'https://api.balldontlie.io/atp/v1/match_stats',
headers={'Authorization': 'YOUR_API_KEY'},
params={'match_ids[]': 50800} # 2025 US Open Final
)
if response.status_code == 200:
stats = response.json()['data']
for stat in stats:
player = stat['player']['full_name']
aces = stat['aces'] or 0
df = stat['double_faults'] or 0
print(f"{player}: Aces {aces}, Double Faults {df}")
The above command returns JSON structured like this:
{
"data": [
{
"id": 145697,
"match": {
"id": 50800,
"tournament_id": 313,
"season": 2025,
"round": "Finals",
"player1_id": 1,
"player2_id": 2,
"winner_id": 1
},
"player": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"set_number": 0,
"serve_rating": 305,
"aces": 10,
"double_faults": 0,
"first_serve_pct": 61,
"first_serve_points_won_pct": 83,
"second_serve_points_won_pct": 57,
"break_points_saved_pct": 0,
"return_rating": 157,
"first_return_won_pct": 31,
"second_return_won_pct": 52,
"break_points_converted_pct": 45,
"total_service_points_won_pct": 73,
"total_return_points_won_pct": 42,
"total_points_won_pct": 56
},
{
"id": 145698,
"match": {
"id": 50800,
"tournament_id": 313,
"season": 2025,
"round": "Finals",
"player1_id": 1,
"player2_id": 2,
"winner_id": 1
},
"player": {
"id": 2,
"first_name": "Jannik",
"last_name": "Sinner",
"full_name": "Jannik Sinner",
"country": "Italy",
"country_code": "ITA",
"birth_place": "Italy",
"age": 24,
"height_cm": 191,
"weight_kg": 77,
"plays": "Right-Handed",
"turned_pro": 2018
},
"set_number": 0,
"serve_rating": 233,
"aces": 2,
"double_faults": 4,
"first_serve_pct": 48,
"first_serve_points_won_pct": 69,
"second_serve_points_won_pct": 48,
"break_points_saved_pct": 55,
"return_rating": 165,
"first_return_won_pct": 17,
"second_return_won_pct": 43,
"break_points_converted_pct": 100,
"total_service_points_won_pct": 58,
"total_return_points_won_pct": 27,
"total_points_won_pct": 44
}
],
"meta": {
"per_page": 25
}
}
This endpoint retrieves detailed match statistics for players.
HTTP Request
GET https://api.balldontlie.io/atp/v1/match_stats
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| match_ids | array | Filter by specific match IDs |
| player_ids | array | Filter statistics for specific players |
| set_number | integer | Filter by set number (0 for full match stats) |
Player Career Statistics
Get Player Career Statistics
curl "https://api.balldontlie.io/atp/v1/player_career_stats?player_ids[]=1&player_ids[]=2" \
-H "Authorization: YOUR_API_KEY"
const params = new URLSearchParams();
params.append('player_ids[]', 1); // Alcaraz
params.append('player_ids[]', 2); // Sinner
const response = await fetch(`https://api.balldontlie.io/atp/v1/player_career_stats?${params}`, {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
# Get career stats for Alcaraz and Sinner (2025 US Open finalists)
response = requests.get(
'https://api.balldontlie.io/atp/v1/player_career_stats',
headers={'Authorization': 'YOUR_API_KEY'},
params=[('player_ids[]', 1), ('player_ids[]', 2)]
)
if response.status_code == 200:
stats = response.json()['data']
for stat in stats:
player = stat['player']['full_name']
titles = stat['career_titles'] or 0
prize = stat['career_prize_money'] or 0
print(f"{player}: {titles} titles, ${prize:,.0f} career earnings")
The above command returns JSON structured like this:
{
"data": [
{
"player": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"career_titles": 24,
"career_prize_money": 60032046,
"singles_wins": 280,
"singles_losses": 65,
"ytd_wins": 0,
"ytd_losses": 0,
"ytd_titles": 0
},
{
"player": {
"id": 2,
"first_name": "Jannik",
"last_name": "Sinner",
"full_name": "Jannik Sinner",
"country": "Italy",
"country_code": "ITA",
"birth_place": "Italy",
"age": 24,
"height_cm": 191,
"weight_kg": 77,
"plays": "Right-Handed",
"turned_pro": 2018
},
"career_titles": 24,
"career_prize_money": 56632426,
"singles_wins": 321,
"singles_losses": 86,
"ytd_wins": 0,
"ytd_losses": 0,
"ytd_titles": 0
}
],
"meta": {
"next_cursor": 2,
"per_page": 25
}
}
This endpoint retrieves career statistics for players.
HTTP Request
GET https://api.balldontlie.io/atp/v1/player_career_stats
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| cursor | integer | Cursor for pagination |
| per_page | integer | Number of results per page (max 100) |
| player_ids | array | Filter statistics for specific players |
Head to Head
Get Head to Head Record
curl "https://api.balldontlie.io/atp/v1/head_to_head?player1_id=1&player2_id=2" \
-H "Authorization: YOUR_API_KEY"
// Get Alcaraz vs Sinner head-to-head (2025 US Open finalists)
const response = await fetch('https://api.balldontlie.io/atp/v1/head_to_head?player1_id=1&player2_id=2', {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
# Get head-to-head record between Alcaraz and Sinner
response = requests.get(
'https://api.balldontlie.io/atp/v1/head_to_head',
headers={'Authorization': 'YOUR_API_KEY'},
params={'player1_id': 1, 'player2_id': 2} # Alcaraz vs Sinner
)
if response.status_code == 200:
h2h = response.json()['data']
p1 = h2h['player1']['full_name']
p2 = h2h['player2']['full_name']
print(f"{p1} vs {p2}: {h2h['player1_wins']}-{h2h['player2_wins']}")
The above command returns JSON structured like this:
{
"data": {
"id": 1,
"player1": {
"id": 1,
"first_name": "Carlos",
"last_name": "Alcaraz",
"full_name": "Carlos Alcaraz",
"country": "Spain",
"country_code": "ESP",
"birth_place": "Spain",
"age": 22,
"height_cm": 183,
"weight_kg": 74,
"plays": "Right-Handed",
"turned_pro": 2018
},
"player2": {
"id": 2,
"first_name": "Jannik",
"last_name": "Sinner",
"full_name": "Jannik Sinner",
"country": "Italy",
"country_code": "ITA",
"birth_place": "Italy",
"age": 24,
"height_cm": 191,
"weight_kg": 77,
"plays": "Right-Handed",
"turned_pro": 2018
},
"player1_wins": 10,
"player2_wins": 6
}
}
This endpoint retrieves the head-to-head record between two players.
HTTP Request
GET https://api.balldontlie.io/atp/v1/head_to_head
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| player1_id | integer | Yes | The ID of the first player |
| player2_id | integer | Yes | The ID of the second player |
Betting Odds
Get Betting Odds
curl "https://api.balldontlie.io/atp/v1/odds?tournament_ids[]=313&season=2025" \
-H "Authorization: YOUR_API_KEY"
const params = new URLSearchParams();
params.append('tournament_ids[]', 313); // US Open
params.append('season', 2025);
const response = await fetch(`https://api.balldontlie.io/atp/v1/odds?${params}`, {
headers: {
'Authorization': 'YOUR_API_KEY'
}
});
const data = await response.json();
console.log(data);
import requests
# Get betting odds for the 2025 US Open
response = requests.get(
'https://api.balldontlie.io/atp/v1/odds',
headers={'Authorization': 'YOUR_API_KEY'},
params={'tournament_ids[]': 313, 'season': 2025} # US Open 2025
)
if response.status_code == 200:
odds = response.json()['data']
for odd in odds:
p1 = odd['player1']['full_name'] if odd['player1'] else 'TBD'
p2 = odd['player2']['full_name'] if odd['player2'] else 'TBD'
print(f"{odd['vendor']}: {p1} ({odd['player1_odds']}) vs {p2} ({odd['player2_odds']})")
The above command returns JSON structured like this:
{
"data": [
{
"id": 78250352,
"match_id": 134142,
"vendor": "caesars",
"player1": {
"id": 81,
"first_name": "Alejandro",
"last_name": "Tabilo",
"full_name": "Alejandro Tabilo",
"country": "Chile",
"country_code": "CHI",
"birth_place": "Canada",
"age": 28,
"height_cm": 188,
"weight_kg": 75,
"plays": "Left-Handed",
"turned_pro": 2015
},
"player2": {
"id": 48,
"first_name": "Camilo",
"last_name": "Ugo Carabelli",
"full_name": "Camilo Ugo Carabelli",
"country": "Argentina",
"country_code": "ARG",
"birth_place": "Argentina",
"age": 26,
"height_cm": 185,
"weight_kg": 83,
"plays": "Right-Handed",
"turned_pro": 2016
},
"player1_odds": 3000,
"player2_odds": -100000,
"updated_at": "2026-01-12T23:40:23.936Z"
},
{
"id": 78250387,
"match_id": 134142,
"vendor": "draftkings",
"player1": {
"id": 81,
"first_name": "Alejandro",
"last_name": "Tabilo",
"full_name": "Alejandro Tabilo",
"country": "Chile",
"country_code": "CHI",
"birth_place": "Canada",
"age": 28,
"height_cm": 188,
"weight_kg": 75,
"plays": "Left-Handed",
"turned_pro": 2015
},
"player2": {
"id": 48,
"first_name": "Camilo",
"last_name": "Ugo Carabelli",
"full_name": "Camilo Ugo Carabelli",
"country": "Argentina",
"country_code": "ARG",
"birth_place": "Argentina",
"age": 26,
"height_cm": 185,
"weight_kg": 83,
"plays": "Right-Handed",
"turned_pro": 2016
},
"player1_odds": 6000,
"player2_odds": -50000,
"updated_at": "2026-01-12T23:35:42.637Z"
},
{
"id": 78250279,
"match_id": 134142,
"vendor": "fanduel",
"player1": {
"id": 81,
"first_name": "Alejandro",
"last_name": "Tabilo",
"full_name": "Alejandro Tabilo",
"country": "Chile",
"country_code": "CHI",
"birth_place": "Canada",
"age": 28,
"height_cm": 188,
"weight_kg": 75,
"plays": "Left-Handed",
"turned_pro": 2015
},
"player2": {
"id": 48,
"first_name": "Camilo",
"last_name": "Ugo Carabelli",
"full_name": "Camilo Ugo Carabelli",
"country": "Argentina",
"country_code": "ARG",
"birth_place": "Argentina",
"age": 26,
"height_cm": 185,
"weight_kg": 83,
"plays": "Right-Handed",
"turned_pro": 2016
},
"player1_odds": -50000,
"player2_odds": 10000,
"updated_at": "2026-01-12T23:40:09.005Z"
}
]
}
This endpoint retrieves betting odds for ATP matches from various sportsbooks.
Important: At least one of match_ids, tournament_ids, player_ids, or season is required.
HTTP Request
GET https://api.balldontlie.io/atp/v1/odds
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| match_ids | array | Filter odds by match IDs |
| tournament_ids | array | Filter odds by tournament IDs |
| player_ids | array | Filter odds by player IDs |
| season | integer | Filter odds by season year |
Supported Vendors
draftkingsfanduelcaesars
Odds Format
Odds are returned in American format: - Negative odds (e.g., -180) indicate the favorite and show how much you need to bet to win $100 - Positive odds (e.g., +150) indicate the underdog and show how much you win on a $100 bet