OpenDota API endpoint
Returns about 100 recent ranked public Dota 2 matches with the heroes on each side, game mode, lobby type and average rank tier, optionally limited to a rank range. Rank tiers are the medal (1 Herald, 2 Guardian, 3 Crusader, 4 Archon, 5 Legend, 6 Ancient, 7 Divine, 8 Immortal) times ten plus stars 1 to 5. Page back with `less_than_match_id` set to the previous `next_less_than_match_id`. Players' account ids are not returned.
/opendota/public-matchesHeader: x-api-key required.
Parameters:
min_rank: optional lowest average rank tier. Allowed values: 11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 31, 32, 33, 34, 35, 41, 42, 43, 44, 45, 51, 52, 53, 54, 55, 61, 62, 63, 64, 65, 71, 72, 73, 74, 75, 81, 82, 83, 84, 85. A tier is the medal (1 Herald, 2 Guardian, 3 Crusader, 4 Archon, 5 Legend, 6 Ancient, 7 Divine, 8 Immortal) times ten plus stars 1 to 5, so 71 is Divine 1. Other values return 400.max_rank: optional highest average rank tier, same allowed values as min_rank; it must not be below min_rank.less_than_match_id: optional positive match id; returns matches older than it. Page back with the previous response's next_less_than_match_id.Notable response behavior:
radiant_heroes and dire_heroes (hero id and localized name). Hidden slots are left out, so a lineup can be shorter than five or empty.avg_rank_tier and avg_rank_medal describe the match's ranked players (ranked_players is how many were ranked). Players' account ids are never returned.lobby_type and game_mode are the numeric ids described by opendota-constants. The response is cached for up to five minutes.| Field | Filled in, of 100 checked | Values in the sample |
|---|---|---|
| match_id | 100 | |
| start_time | 100 | 2026-10-29 |
| duration_seconds | 100 | 258 to 1,920, median 1,320 |
| radiant_win | 100 | true in 58 of 100 |
| lobby_type | 100 | 0 to 7, median 0 |
| game_mode | 100 | 1 to 23, median 23 |
| avg_rank_tier | 100 | 71 to 75, median 73 |
| avg_rank_medal | 100 | Divine in all 100 |
| ranked_players | 100 | 1 to 9, median 3 |
| cluster | 100 | 121 to 441, median 201 |
| radiant_heroes | 100 | median 5 entries |
| dire_heroes | 100 | median 5 entries |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| x-api-key (header) | string | Yes | API key required | ||
| min_rank | integer | No | Lowest average rank tier, medal 1-8 times ten plus stars 1-5 Allowed values: 11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 31, 32, 33, 34, 35, 41, 42, 43, 44, 45, 51, 52, 53, 54, 55, 61, 62, 63, 64, 65, 71, 72, 73, 74, 75, 81, 82, 83, 84, 85. | ||
| max_rank | integer | No | Highest average rank tier, same form as min_rank; not below min_rank Allowed values: 11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 31, 32, 33, 34, 35, 41, 42, 43, 44, 45, 51, 52, 53, 54, 55, 61, 62, 63, 64, 65, 71, 72, 73, 74, 75, 81, 82, 83, 84, 85. | ||
| less_than_match_id | integer | No | Return matches older than this match id | 9066000000 |
curl -X GET "https://api.crawlora.net/api/v1/opendota/public-matches?min_rank=11&max_rank=11" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"msg": "OK",
"data": {
"source_url": "https://api.opendota.com/api/publicMatches?max_rank=85&min_rank=71",
"fetched_at": "2026-10-29T01:20:00Z",
"min_rank": 71,
"max_rank": 85,
"count": 1,
"next_less_than_match_id": 9066504584,
"matches": [
{
"match_id": 9066504584,
"start_time": "2026-10-29T01:16:41Z",
"duration_seconds": 1329,
"radiant_win": true,
"lobby_type": 0,
"game_mode": 22,
"avg_rank_tier": 71,
"avg_rank_medal": "Divine",
"ranked_players": 6,
"cluster": 171,
"radiant_heroes": [
{
"hero_id": 1,
"localized_name": "Anti-Mage"
}
],
"dire_heroes": [
{
"hero_id": 2,
"localized_name": "Axe"
}
]
}
]
}
}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.PublicMatchesResponse | No | ||||
| data.count | integer | No | 100 | |||
| data.fetched_at | string | No | 2026-10-27T10:00:00Z | |||
| data.matches | array | No | ||||
| data.matches[].avg_rank_medal | string | No | Divine | |||
| data.matches[].avg_rank_tier | integer | No | 71 | |||
| data.matches[].cluster | integer | No | 171 | |||
| data.matches[].dire_heroes | array | No | ||||
| data.matches[].dire_heroes[].hero_id | integer | No | 1 | |||
| data.matches[].dire_heroes[].localized_name | string | No | Anti-Mage | |||
| data.matches[].duration_seconds | integer | No | 1329 | |||
| data.matches[].game_mode | integer | No | 22 | |||
| data.matches[].lobby_type | integer | No | 0 | |||
| data.matches[].match_id | integer | No | 9066504584 | |||
| data.matches[].radiant_heroes | array | No | ||||
| data.matches[].radiant_heroes[].hero_id | integer | No | 1 | |||
| data.matches[].radiant_heroes[].localized_name | string | No | Anti-Mage | |||
| data.matches[].radiant_win | boolean | No | true | |||
| data.matches[].ranked_players | integer | No | 6 | |||
| data.matches[].start_time | string | No | 2026-10-29T01:16:41Z | |||
| data.max_rank | integer | No | 85 | |||
| data.min_rank | integer | No | 71 | |||
| data.next_less_than_match_id | integer | No | 9066000000 | |||
| 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/public-matches?min_rank=11&max_rank=11" \
-H "x-api-key: $CRAWLORA_API_KEY"