NAV
shell javascript python

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:

  1. Copy this URL: https://www.balldontlie.io/openapi.yml
  2. Share it with your preferred AI assistant (ChatGPT, Claude, Gemini, etc.)
  3. Tell the AI what you want to build (e.g., "Create a dashboard showing ATP rankings")
  4. The AI will read the OpenAPI spec and write the code for you

Example prompts to try:

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_KEY with 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

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