OpenDota API endpoint
Returns every Dota 2 hero with base stats (health, mana, armor, attack range, move speed) and OpenDota's aggregate pick/win counts and win rates for professional games (with bans), public games, turbo, and each of the eight rank brackets (`Herald`, `Guardian`, `Crusader`, `Archon`, `Legend`, `Ancient`, `Divine`, `Immortal`). Filter by `primary_attr` (`agi`, `all`, `int`, `str`), `attack_type` (`Melee`, `Ranged`) and `role`, sort with `sort` and `desc`, and cap with `limit`. Win rates are percentages and are omitted when there are no picks.
/opendota/hero-statsReturns Dota 2 hero base stats with professional, public, turbo and per-rank-bracket pick and win statistics.
Header: x-api-key required.
Parameters:
primary_attr: optional; allowed values: agi, all, int, str.attack_type: optional; allowed values: Melee, Ranged.role: optional; allowed values: Carry, Disabler, Durable, Escape, Initiator, Nuker, Pusher, Support.sort: optional; allowed values: id, name, pro_pick, pro_win, pro_ban, pro_win_rate, pub_pick, pub_win, pub_win_rate, turbo_pick, turbo_win_rate. Default id.desc: optional boolean. Numeric keys sort descending by default.limit: optional 1 to 200; default all heroes.Notable response behavior:
rank_brackets[].bracket has these values: 1, 2, 3, 4, 5, 6, 7, 8 (matching the names below in order).rank_brackets[].name has these values: Herald, Guardian, Crusader, Archon, Legend, Ancient, Divine, Immortal; counts cover recent public games of that bracket.pro counts reflect the current professional window and can be small right after a patch; pro.bans is only reported for professional games.roles[] values: Carry, Disabler, Durable, Escape, Initiator, Nuker, Pusher, Support. Filters and sorting run locally on one cached source read. The response is cached for up to one hour.| Field | Filled in, of 40 checked | Values in the sample |
|---|---|---|
| id | 40 | |
| name | 40 | “npc_dota_hero_antimage” and 39 other values |
| localized_name | 40 | “Anti-Mage” and 39 other values |
| primary_attr | 40 | agi (19), str (11), int (7) and 1 more |
| attack_type | 40 | Ranged (22), Melee (18) |
| roles | 40 | median 4 entries |
| base_health | 40 | 120 |
| base_mana | 40 | 0 to 75, median 75 |
| base_armor | 40 | -2 to 2, median 0 |
| base_attack_min | 40 | 12 to 40, median 26 |
| base_attack_max | 40 | 19 to 44, median 32 |
| attack_range | 40 | 150 to 670, median 330 |
| move_speed | 40 | 275 to 325, median 295 |
| pro | 40 | |
| public | 40 | |
| turbo | 40 | |
| rank_brackets | 40 | median 8 entries |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| x-api-key (header) | string | Yes | API key required | ||
| primary_attr | string | No | Primary attribute filter; one of agi, all, int, str Allowed values: agi, all, int, str. | ||
| attack_type | string | No | Attack type filter; one of Melee, Ranged Allowed values: Melee, Ranged. | ||
| role | string | No | Role filter; one of Carry, Disabler, Durable, Escape, Initiator, Nuker, Pusher, Support Allowed values: Carry, Disabler, Durable, Escape, Initiator, Nuker, Pusher, Support. | ||
| sort | string | No | id | Sort key; one of id, name, pro_pick, pro_win, pro_ban, pro_win_rate, pub_pick, pub_win, pub_win_rate, turbo_pick, turbo_win_rate; defaults to id Allowed values: id, name, pro_pick, pro_win, pro_ban, pro_win_rate, pub_pick, pub_win, pub_win_rate, turbo_pick, turbo_win_rate. | |
| desc | boolean | No | Sort descending (the default for every key except id and name) | ||
| limit | integer | No | all | Number of heroes to return, 1 to 200; defaults to all Minimum: 1. Maximum: 200. |
curl -X GET "https://api.crawlora.net/api/v1/opendota/hero-stats?primary_attr=agi&attack_type=Melee&role=Carry&sort=id&desc=true&limit=10" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"msg": "OK",
"data": {
"source_url": "https://api.opendota.com/api/hero-stats",
"fetched_at": "2026-10-28T10:00:00Z",
"count": 1,
"heroes": [
{
"id": 1,
"name": "npc_dota_hero_antimage",
"localized_name": "Anti-Mage",
"primary_attr": "agi",
"attack_type": "Melee",
"roles": [
"Carry",
"Escape",
"Nuker"
],
"base_health": 120,
"base_mana": 75,
"base_armor": 2,
"base_attack_min": 29,
"base_attack_max": 33,
"attack_range": 150,
"move_speed": 310,
"pro": {
"picks": 13,
"wins": 7,
"bans": 36,
"win_rate_percent": 53.85
},
"public": {
"picks": 696061,
"wins": 355934,
"win_rate_percent": 51.13
},
"turbo": {
"picks": 279477,
"wins": 141948,
"win_rate_percent": 50.79
},
"rank_brackets": [
{
"bracket": 1,
"name": "Herald",
"picks": 12570,
"wins": 6479,
"win_rate_percent": 51.54
}
]
}
]
}
}Request schema
No body schema
Response schema
Inline response schema
| Field | Type | Required | Enum | Bounds | Example | Description |
|---|---|---|---|---|---|---|
| code | integer | No | Code is the HTTP status code or a custom code used to indicate the result of the request @example 200 | |||
| data | opendota.HeroStatsResponse | No | ||||
| data.count | integer | No | 127 | |||
| data.fetched_at | string | No | 2026-10-27T10:00:00Z | |||
| data.heroes | array | No | ||||
| data.heroes[].attack_range | integer | No | 150 | |||
| data.heroes[].attack_type | string | No | Melee, Ranged | Melee | ||
| data.heroes[].base_armor | number | No | 2 | |||
| data.heroes[].base_attack_max | integer | No | 33 | |||
| data.heroes[].base_attack_min | integer | No | 29 | |||
| data.heroes[].base_health | integer | No | 120 | |||
| data.heroes[].base_mana | integer | No | 75 | |||
| data.heroes[].id | integer | No | 1 | |||
| data.heroes[].localized_name | string | No | Anti-Mage | |||
| data.heroes[].move_speed | integer | No | 310 | |||
| data.heroes[].name | string | No | npc_dota_hero_antimage | |||
| data.heroes[].primary_attr | string | No | agi, all, int, str | agi | ||
| data.heroes[].pro | opendota.PickWin | No | ||||
| data.heroes[].pro.bans | integer | No | 36 | |||
| data.heroes[].pro.picks | integer | No | 13 | |||
| data.heroes[].pro.win_rate_percent | number | No | 53.85 | |||
| data.heroes[].pro.wins | integer | No | 7 | |||
| data.heroes[].public | opendota.PickWin | No | ||||
| data.heroes[].public.bans | integer | No | 36 | |||
| data.heroes[].public.picks | integer | No | 13 | |||
| data.heroes[].public.win_rate_percent | number | No | 53.85 | |||
| data.heroes[].public.wins | integer | No | 7 | |||
| data.heroes[].rank_brackets | array | No | ||||
| data.heroes[].rank_brackets[].bracket | integer | No | 1, 2, 3, 4, 5, 6, 7, 8 | 5 | ||
| data.heroes[].rank_brackets[].name | string | No | Herald, Guardian, Crusader, Archon, Legend, Ancient, Divine, Immortal | Legend | ||
| data.heroes[].rank_brackets[].picks | integer | No | 173376 | |||
| data.heroes[].rank_brackets[].win_rate_percent | number | No | 51.23 | |||
| data.heroes[].rank_brackets[].wins | integer | No | 88824 | |||
| data.heroes[].roles | array | No | Carry | |||
| data.heroes[].turbo | opendota.PickWin | No | ||||
| data.heroes[].turbo.bans | integer | No | 36 | |||
| data.heroes[].turbo.picks | integer | No | 13 | |||
| data.heroes[].turbo.win_rate_percent | number | No | 53.85 | |||
| data.heroes[].turbo.wins | integer | No | 7 | |||
| data.source_url | string | No | ||||
| msg | unknown | No | Msg is the message that describes the result of the request @example "Request successful" |
| Status | Description | Schema |
|---|---|---|
| 400 | Bad Request | #/definitions/app.Response |
| 429 | Too Many Requests | #/definitions/app.Response |
| 503 | Service Unavailable | #/definitions/app.Response |
Use environment variables for secrets and keep Crawlora API keys server-side.
curl -X GET "https://api.crawlora.net/api/v1/opendota/hero-stats?primary_attr=agi&attack_type=Melee&role=Carry&sort=id&desc=true&limit=10" \
-H "x-api-key: $CRAWLORA_API_KEY"