Data Models¶
🤖 AI Summary
Pydantic models for API responses. Match models: Match (full data with players, teams, league, draft), PublicMatch (summary with avg_mmr), ProMatch (with team/league names). Player models: PlayerProfile (profile + rank_tier), PlayerMatch (hero_id, KDA, player_slot), Player (detailed match player with laning, combat, economy data). Hero models: Hero (name, roles, primary_attr), HeroStats (pro_pick/win/ban, rank-specific picks). New in 7.40: Team objects, league objects, draft timings, chat messages, detailed player analytics.
All API responses are parsed into Pydantic models with full type safety.
Match Models¶
Match¶
Detailed match data returned by get_match().
class Match(BaseModel):
match_id: int
duration: int
radiant_win: bool
radiant_score: int
dire_score: int
players: List[Player]
# Team information (pro matches)
radiant_team_id: Optional[int]
radiant_name: Optional[str]
dire_team_id: Optional[int]
dire_name: Optional[str]
radiant_team: Optional[MatchTeam]
dire_team: Optional[MatchTeam]
# League information
league: Optional[MatchLeague]
# Draft (Captains Mode)
draft_timings: Optional[List[DraftTiming]]
# Analysis
comeback: Optional[float]
stomp: Optional[float]
# Chat
chat: Optional[List[ChatMessage]]
Core Fields:
| Field | Type | Description |
|---|---|---|
match_id |
int |
Unique match identifier |
duration |
int |
Match duration in seconds |
radiant_win |
bool |
Whether Radiant won |
radiant_score |
int |
Radiant kill score |
dire_score |
int |
Dire kill score |
players |
List[Player] |
List of player data |
game_mode |
int |
Game mode ID |
lobby_type |
int |
Lobby type ID |
Team Fields (Pro Matches):
| Field | Type | Description |
|---|---|---|
radiant_team_id |
Optional[int] |
Radiant team ID |
radiant_name |
Optional[str] |
Radiant team name |
radiant_logo |
Optional[int] |
Radiant team logo ID |
radiant_captain |
Optional[int] |
Radiant captain account ID |
dire_team_id |
Optional[int] |
Dire team ID |
dire_name |
Optional[str] |
Dire team name |
dire_logo |
Optional[int] |
Dire team logo ID |
dire_captain |
Optional[int] |
Dire captain account ID |
Match Analysis Fields:
| Field | Type | Description |
|---|---|---|
comeback |
Optional[float] |
Comeback indicator score |
stomp |
Optional[float] |
Stomp indicator score |
pre_game_duration |
Optional[int] |
Pre-game phase duration |
flags |
Optional[int] |
Match flags |
MatchTeam¶
Team data embedded within a match.
class MatchTeam(BaseModel):
team_id: Optional[int]
name: Optional[str]
tag: Optional[str]
logo_url: Optional[str]
MatchLeague¶
League information embedded within a match.
class MatchLeague(BaseModel):
leagueid: int
name: Optional[str]
tier: Optional[str] # "premium", "professional", "amateur"
banner: Optional[str]
DraftTiming¶
Draft timing data for each pick/ban in Captains Mode.
class DraftTiming(BaseModel):
order: int # Draft order (0-23)
pick: bool # True if pick, False if ban
active_team: int # Team making the selection
hero_id: int # Hero selected
player_slot: Optional[int]
extra_time: Optional[int]
total_time_taken: Optional[int]
ChatMessage¶
Chat message in a match.
class ChatMessage(BaseModel):
time: int # Game time in seconds
type: Optional[str] # Message type
key: Optional[str] # Chat wheel key or message
slot: Optional[int] # Player slot
player_slot: Optional[int]
Player (Match Player)¶
Detailed player data within a match. Contains extensive analytics for parsed matches.
class Player(BaseModel):
# Core stats
account_id: Optional[int]
player_slot: int
hero_id: int
kills: int
deaths: int
assists: int
# New identity fields
hero_variant: Optional[int] # Hero persona/arcana variant
personaname: Optional[str] # Display name
isRadiant: Optional[bool] # Team indicator
# Items
item_0: Optional[int]
item_1: Optional[int]
# ... item_2 through item_5
item_neutral: Optional[int] # First neutral item
item_neutral2: Optional[int] # Second neutral item (new)
# Laning
lane: Optional[int] # 1=safe, 2=mid, 3=off
lane_role: Optional[int] # Lane role
lane_efficiency: Optional[float]
is_roaming: Optional[bool]
# Combat
kda: Optional[float] # Calculated KDA ratio
tower_kills: Optional[int]
teamfight_participation: Optional[float]
stuns: Optional[float]
# Support stats
obs_placed: Optional[int] # Observer wards placed
sen_placed: Optional[int] # Sentry wards placed
camps_stacked: Optional[int]
# Time series (parsed matches)
gold_t: Optional[List[int]] # Gold per minute
xp_t: Optional[List[int]] # XP per minute
lh_t: Optional[List[int]] # Last hits per minute
# Detailed breakdowns
benchmarks: Optional[Dict] # Performance benchmarks
ability_uses: Optional[Dict] # Ability usage counts
damage: Optional[Dict] # Damage dealt breakdown
Identity Fields:
| Field | Type | Description |
|---|---|---|
hero_variant |
Optional[int] |
Hero persona/arcana variant |
personaname |
Optional[str] |
Player display name |
name |
Optional[str] |
Pro player name |
isRadiant |
Optional[bool] |
True if on Radiant team |
rank_tier |
Optional[int] |
Player rank tier |
Laning Fields:
| Field | Type | Description |
|---|---|---|
lane |
Optional[int] |
Lane (1=safe, 2=mid, 3=off) |
lane_role |
Optional[int] |
Role in lane |
lane_efficiency |
Optional[float] |
Laning efficiency (0-1) |
lane_kills |
Optional[int] |
Kills during laning |
is_roaming |
Optional[bool] |
Roaming indicator |
Combat Fields:
| Field | Type | Description |
|---|---|---|
kda |
Optional[float] |
KDA ratio |
hero_kills |
Optional[int] |
Hero kills |
tower_kills |
Optional[int] |
Tower kills |
roshan_kills |
Optional[int] |
Roshan kills |
teamfight_participation |
Optional[float] |
Teamfight participation (0-1) |
stuns |
Optional[float] |
Stun duration dealt |
Support Fields:
| Field | Type | Description |
|---|---|---|
obs_placed |
Optional[int] |
Observer wards placed |
sen_placed |
Optional[int] |
Sentry wards placed |
camps_stacked |
Optional[int] |
Camps stacked |
rune_pickups |
Optional[int] |
Runes picked up |
PublicMatch¶
Summary data for public matches.
class PublicMatch(BaseModel):
match_id: int
duration: int
radiant_win: bool
avg_mmr: Optional[int]
avg_rank_tier: Optional[float]
game_mode: int
lobby_type: int
ProMatch¶
Professional match data.
class ProMatch(BaseModel):
match_id: int
duration: int
radiant_win: bool
radiant_name: Optional[str]
dire_name: Optional[str]
radiant_team_id: Optional[int]
dire_team_id: Optional[int]
league_name: Optional[str]
leagueid: Optional[int]
Player Models¶
PlayerProfile¶
Player profile and ranking data.
class PlayerProfile(BaseModel):
profile: Profile
rank_tier: Optional[int]
leaderboard_rank: Optional[int]
Profile Sub-model:
class Profile(BaseModel):
account_id: int
personaname: Optional[str]
name: Optional[str]
steamid: Optional[str]
avatar: Optional[str]
loccountrycode: Optional[str]
plus: Optional[bool] # Dota Plus subscriber
is_contributor: Optional[bool]
is_subscriber: Optional[bool]
PlayerMatch¶
Match data from player's perspective (player match history).
class PlayerMatch(BaseModel):
match_id: int
hero_id: int
kills: int
deaths: int
assists: int
player_slot: int
radiant_win: bool
duration: int
game_mode: int
hero_variant: Optional[int] # New in 7.40
Hero Models¶
Hero¶
Basic hero information.
class Hero(BaseModel):
id: int
name: str # Internal name (e.g., "npc_dota_hero_antimage")
localized_name: str # Display name (e.g., "Anti-Mage")
primary_attr: str # "agi", "str", "int", "all"
attack_type: str # "Melee" or "Ranged"
roles: List[str] # ["Carry", "Escape", "Nuker"]
HeroStats¶
Hero statistics with pick/win rates across skill brackets.
class HeroStats(BaseModel):
id: int
localized_name: str
pro_pick: Optional[int]
pro_win: Optional[int]
pro_ban: Optional[int]
pub_pick: Optional[int]
pub_win: Optional[int]
# Per-rank stats (1-8)
field_1_pick: Optional[int] # Herald picks
field_1_win: Optional[int] # Herald wins
# ... through field_8_pick/win (Immortal)
pub_pick_trend: Optional[List[int]]
pub_win_trend: Optional[List[int]]
Usage Examples¶
Basic Match Analysis¶
from opendota import OpenDota
async with OpenDota() as client:
match = await client.get_match(8461956309)
# Access basic properties
print(f"Duration: {match.duration}s")
print(f"Winner: {'Radiant' if match.radiant_win else 'Dire'}")
# Access team info (pro matches)
if match.radiant_name:
print(f"Teams: {match.radiant_name} vs {match.dire_name}")
# Access league info
if match.league:
print(f"Tournament: {match.league.name} ({match.league.tier})")
Detailed Player Analysis¶
async with OpenDota() as client:
match = await client.get_match(8461956309)
for player in match.players:
team = "Radiant" if player.isRadiant else "Dire"
print(f"[{team}] {player.personaname}")
print(f" Hero variant: {player.hero_variant}")
print(f" KDA: {player.kda:.2f}")
print(f" Lane: {player.lane}, Efficiency: {player.lane_efficiency:.1%}")
if player.obs_placed:
print(f" Wards: {player.obs_placed} obs, {player.sen_placed} sen")
if player.gold_t:
print(f" Final gold: {player.gold_t[-1]}")
Draft Analysis¶
async with OpenDota() as client:
match = await client.get_match(8461956309)
if match.draft_timings:
print("Draft order:")
for draft in match.draft_timings:
action = "PICK" if draft.pick else "BAN"
team = "Radiant" if draft.active_team == 0 else "Dire"
print(f" {draft.order}: [{team}] {action} hero {draft.hero_id}")
Pro vs Public Match Data¶
async with OpenDota() as client:
# Pro match - has team/league/draft data
pro = await client.get_match(8461956309)
print(f"Pro match: {pro.radiant_name} vs {pro.dire_name}")
print(f"League: {pro.league.name}")
print(f"Draft entries: {len(pro.draft_timings)}")
# Public match - limited metadata
pub = await client.get_match(8607246638)
print(f"Public match: Team info = {pub.radiant_name}") # None
print(f"League: {pub.league}") # None
print(f"Draft: {pub.draft_timings}") # None or empty