OpenDota API endpoint
Returns OpenDota's scenario datasets as games, wins and win rate rows, most games first. `kind` selects `item-timings` (an item bought by a time for a hero), `lane-roles` (a hero in a lane role at a time) or `misc` (courier kill, first blood and chat scenarios by side and region). Filters are validated before any upstream call: `hero_id` and `item` apply to item-timings, `hero_id` and `lane_role` to lane-roles, `scenario` to misc.
/opendota/scenariosHeader: x-api-key required.
Parameters:
kind: required; allowed values: item-timings, lane-roles, misc.hero_id: optional positive hero id from opendota-heroes; item-timings and lane-roles only.item: optional item name from opendota-constants item_ids, for example bfury; item-timings only. An unknown name returns 400.lane_role: optional 1, 2, 3 or 4 (safe lane, mid, off lane, jungle); lane-roles only.scenario: optional; allowed values: courier_kill, first_blood, neg_chat_1min, pos_chat_1min; misc only.limit: optional number of rows, 1 to 2000; defaults to all.Notable response behavior:
item-timings rows are per hero and item at the time points 450, 600, 720, 900, 1200, 1500 and 1800 seconds (returned as timings); lane-roles rows use the game-duration points 900, 1800, 2700, 3600 and 5400 seconds.misc rows carry scenario, is_radiant and region_id (decode it with opendota-constants region).kind returns 400; an unknown hero returns 404. total is the row count before limit. The response is cached for up to six hours.| Field | Filled in, of 2 checked | Values in the sample |
|---|---|---|
| hero_id | 2 | |
| hero_name | 2 | “Anti-Mage” |
| item | 2 | “blink” |
| time_seconds | 2 | 1,500 to 1,800, median 1,500 |
| games | 2 | 1 |
| wins | 2 | 0 to 1, median 0 |
| win_rate_percent | 2 | 0 to 100, median 0 |
| Parameter | Type | Required | Default | Description | Example |
|---|---|---|---|---|---|
| x-api-key (header) | string | Yes | API key required | ||
| kind | string | Yes | Dataset; one of item-timings, lane-roles, misc Allowed values: item-timings, lane-roles, misc. | item-timings | |
| hero_id | integer | No | Positive hero ID from opendota-heroes; item-timings and lane-roles only | 1 | |
| item | string | No | Item name from opendota-constants item_ids, for example bfury; item-timings only | ||
| lane_role | integer | No | Lane role 1 to 4 (1 safe lane, 2 mid, 3 off lane, 4 jungle); lane-roles only Minimum: 1. Maximum: 4. | ||
| scenario | string | No | Scenario; one of courier_kill, first_blood, neg_chat_1min, pos_chat_1min; misc only Allowed values: courier_kill, first_blood, neg_chat_1min, pos_chat_1min. | ||
| limit | integer | No | all | Number of rows to return, 1 to 2000, most games first; defaults to all Minimum: 1. Maximum: 2000. |
curl -X GET "https://api.crawlora.net/api/v1/opendota/scenarios?kind=item-timings&scenario=courier_kill&limit=10" \ -H "x-api-key: $CRAWLORA_API_KEY"
{
"code": 200,
"msg": "OK",
"data": {
"source_url": "https://api.opendota.com/api/scenarios",
"fetched_at": "2026-10-28T10:00:00Z",
"kind": "item-timings",
"total": 7,
"count": 1,
"timings": [
450,
600,
720,
900,
1200,
1500,
1800
],
"rows": [
{
"hero_id": 1,
"hero_name": "Anti-Mage",
"item": "bfury",
"time_seconds": 900,
"games": 10,
"wins": 7,
"win_rate_percent": 70
}
]
}
}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.ScenariosResponse | No | ||||
| data.count | integer | No | 7 | |||
| data.fetched_at | string | No | 2026-10-27T10:00:00Z | |||
| data.kind | string | No | item-timings, lane-roles, misc | item-timings | ||
| data.rows | array | No | ||||
| data.rows[].games | integer | No | 1023 | |||
| data.rows[].hero_id | integer | No | 1 | |||
| data.rows[].hero_name | string | No | Anti-Mage | |||
| data.rows[].is_radiant | boolean | No | true | |||
| data.rows[].item | string | No | bfury | |||
| data.rows[].lane_role | integer | No | 2 | |||
| data.rows[].region_id | integer | No | 3 | |||
| data.rows[].scenario | string | No | courier_kill | |||
| data.rows[].time_seconds | integer | No | 900 | |||
| data.rows[].win_rate_percent | number | No | 49.76 | |||
| data.rows[].wins | integer | No | 509 | |||
| data.source_url | string | No | ||||
| data.timings | array | No | ||||
| data.total | integer | No | 1600 | |||
| 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 |
| 404 | Not Found | #/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/scenarios?kind=item-timings&scenario=courier_kill&limit=10" \
-H "x-api-key: $CRAWLORA_API_KEY"