# OpenDota Dota 2 Scraper (`crawlerbros/opendota-scraper`) Actor

Scrape OpenDota.com - the open-source Dota 2 analytics platform. Fetch all 127 heroes with stats, roles, and attributes. Browse recent public matches with MMR data. Lookup player profiles by Steam account ID. Free public API, no key required.

- **URL**: https://apify.com/crawlerbros/opendota-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Developer tools, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## OpenDota Scraper

Scrape **OpenDota** — the open Dota 2 statistics platform. Pull the full hero roster with attributes, attack types and roles, stream recent public matches with skill-bracket filtering, look up a player's profile and rank, or fetch specific matches by ID. Every match record includes the winning side, duration, average MMR and rank tier, game mode, and lobby type. HTTP-only via the public `api.opendota.com` API. No auth, no proxy.

### What this actor does

- **Four modes:** `heroes`, `publicMatches`, `byPlayer`, `byMatch`
- **Hero filters:** primary attribute (Strength / Agility / Intelligence / Universal), attack type, and any of nine roles
- **Skill-bracket filtering:** min/max average MMR on public matches
- **Human-readable derivations:** Unix timestamps also emitted as ISO strings, match duration also in minutes
- **Empty fields are omitted**

### Output: per hero (mode = `heroes`)

- `heroId` — OpenDota hero ID
- `name` — display name, e.g. `Anti-Mage`
- `internalName` — engine identifier, e.g. `npc_dota_hero_antimage`
- `primaryAttr` — `str` / `agi` / `int` / `all`
- `attackType` — `Melee` or `Ranged`
- `roles` — e.g. `Carry`, `Escape`, `Nuker`
- `legs` — number of legs the hero model has
- `sourceUrl` — official hero page on dota2.com
- `recordType: "hero"`, `scrapedAt`

### Output: per match (mode = `publicMatches` / `byMatch`)

- `matchId` — Dota 2 match ID
- `radiantWin` — true when Radiant won
- `startTime` — Unix timestamp
- `startTimeIso` — the same moment as an ISO 8601 UTC string
- `durationSeconds`, `durationMinutes`
- `avgMmr` — average matchmaking rating of the lobby
- `avgRankTier` — average rank tier (medal) of the lobby
- `gameMode` — Dota 2 game mode code
- `lobbyType` — lobby type code
- `numHumanPlayers` — human players in the match
- `leagueId` — league ID, for professional matches
- `sourceUrl` — match page on opendota.com
- `recordType: "match"`, `scrapedAt`

### Output: per player (mode = `byPlayer`)

- `accountId` — Steam account ID (32-bit)
- `personaname` — current Steam display name
- `name` — pro player name, when the account is a registered pro
- `steamId` — 64-bit Steam ID
- `countryCode` — profile country
- `mmrEstimate` — OpenDota's estimated MMR
- `rankTier` — rank medal tier
- `competitiveRank`, `soloCompetitiveRank` — public ranks, when the player exposes them
- `avatarUrl` — full-size Steam avatar
- `profileUrl` — Steam community profile
- `sourceUrl` — player page on opendota.com
- `recordType: "player"`, `scrapedAt`

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `heroes` | `heroes` / `publicMatches` / `byPlayer` / `byMatch` |
| `primaryAttr` | string | – | `str` / `agi` / `int` / `all` (mode=heroes) |
| `attackType` | string | – | `Melee` / `Ranged` (mode=heroes) |
| `role` | string | – | `Carry` / `Support` / `Nuker` / `Disabler` / `Jungler` / `Durable` / `Escape` / `Pusher` / `Initiator` (mode=heroes) |
| `accountId` | int | – | Steam account ID (mode=byPlayer) |
| `matchId` | int | – | Single match ID (mode=byMatch) |
| `matchIds` | array | – | Multiple match IDs (mode=byMatch) |
| `minMmr` | int | – | Keep matches with average MMR at or above this (mode=publicMatches) |
| `maxMmr` | int | – | Keep matches with average MMR at or below this (mode=publicMatches) |
| `maxItems` | int | `50` | Hard cap (1–500) |

#### Example: the full hero roster

```json
{
  "mode": "heroes",
  "maxItems": 500
}
```

#### Example: ranged support heroes

```json
{
  "mode": "heroes",
  "attackType": "Ranged",
  "role": "Support"
}
```

#### Example: high-MMR public matches

```json
{
  "mode": "publicMatches",
  "minMmr": 5000,
  "maxItems": 200
}
```

#### Example: a player profile and specific matches

```json
{
  "mode": "byPlayer",
  "accountId": 86745912
}
```

### Use cases

- **Esports analytics** — sample public matches by skill bracket to measure meta trends
- **Coaching tools** — pull a player's rank and profile to benchmark against a bracket
- **Draft assistants** — build hero pools filtered by attribute, attack type, and role
- **Content & community sites** — populate hero pages and match recaps automatically
- **Machine learning** — assemble labelled match datasets with MMR, duration, and outcome
- **Betting and prediction research** — study win rates by lobby type and skill tier

### FAQ

**What is OpenDota?**  An open-source Dota 2 data platform that ingests match data from Valve's API and republishes it through a free public API. See [opendota.com](https://www.opendota.com).

**Is this affiliated with OpenDota or Valve?**  No. This is a third-party actor using OpenDota's public API.

**Do I need an API key?**  No. The endpoints used are free and unauthenticated. OpenDota rate-limits anonymous callers, so the actor paces its requests and retries on 429.

**Where do I find a Steam account ID?**  It's the 32-bit ID that appears in an OpenDota player URL (`opendota.com/players/<accountId>`). It is not the 64-bit `steamId`, which the actor returns separately.

**Why do some public matches have no `avgMmr`?**  MMR is only exposed for lobbies where enough players have public match data. When it's missing the field is dropped — and note that setting `minMmr` or `maxMmr` therefore excludes those matches.

**What is `avgRankTier`?**  A two-digit code where the first digit is the medal (1 = Herald … 8 = Immortal) and the second is the star within it. `54` means Legend 4.

**What do `gameMode` and `lobbyType` numbers mean?**  They're Valve's own enum codes — for example game mode `22` is All Pick (Ranked) and lobby type `7` is Ranked Matchmaking. OpenDota publishes the full code tables.

**Can I get per-player detail inside a match?**  `byMatch` returns match-level summary fields. Full per-player breakdowns are available on the linked `sourceUrl` match page.

**Why does `byPlayer` sometimes return nothing?**  Players can hide their Dota profile from public stats. When a profile is private the API returns no usable record and the run ends with an explanatory status message.

**How fresh is the data?**  Public matches appear within minutes of finishing. Hero data changes only when Valve ships a patch.

# Actor input Schema

## `mode` (type: `string`):

What to fetch from OpenDota.

## `primaryAttr` (type: `string`):

Filter heroes by primary attribute (mode=heroes).

## `attackType` (type: `string`):

Filter heroes by attack type (mode=heroes).

## `role` (type: `string`):

Filter heroes by role (mode=heroes).

## `accountId` (type: `integer`):

Steam account ID for player profile (mode=byPlayer).

## `matchId` (type: `integer`):

Specific match ID to fetch (mode=byMatch).

## `matchIds` (type: `array`):

Multiple match IDs to fetch.

## `minMmr` (type: `integer`):

Only include public matches with average MMR at least this value.

## `maxMmr` (type: `integer`):

Only include public matches with average MMR at most this value.

## `maxItems` (type: `integer`):

Maximum number of records to emit.

## Actor input object example

```json
{
  "mode": "heroes",
  "primaryAttr": "",
  "attackType": "",
  "role": "",
  "matchIds": [],
  "maxItems": 50
}
```

# Actor output Schema

## `items` (type: `string`):

Dataset containing all scraped records.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "heroes",
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/opendota-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "heroes",
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/opendota-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "heroes",
  "maxItems": 50
}' |
apify call crawlerbros/opendota-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/opendota-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/EN1IFDSgFrLfbC2at/builds/G9UfDWnYV7Orvvwve/openapi.json
