Open-source, self-hostable Bittensor network explorer and API. A drop-in alternative to TaoStats and TaoMarketCap.
No API-level rate limits unlike TaoStats (5 req/min free tier), your self-hosted instance has no request caps. The underlying subtensor RPC nodes (e.g. public Finney endpoints) do have their own connection limits. For heavy usage or production deployments, point OpenTaoAPI at your own subtensor node:
SUBTENSOR_ENDPOINT=ws://your-node:9944Web UI at http://localhost:8000 | Swagger docs at http://localhost:8000/docs
- REST API with full subnet, neuron, emission, and portfolio data
- TaoStats-compatible
/miner/endpoint for drop-in replacement - Web dashboard: portfolio viewer, subnets overview, miners/validators tables
- Direct chain queries via Bittensor SDK (no third-party APIs except MEXC for price)
- Historical data: SQLite storage with epoch-resolution snapshots via public archive node, live polling
- Backfill script pulls directly from chain — no third-party API needed
- In-memory caching with configurable TTLs
- Self-hostable with Docker or conda
conda create -n tao python=3.11 -y
conda activate tao
pip install -r requirements.txt
uvicorn api.main:app --host 0.0.0.0 --port 8000First startup takes ~15-20s for the initial metagraph sync. Subsequent requests are instant from cache.
docker-compose up -d| Page | URL | Description |
|---|---|---|
| Subnets | /subnets |
All subnets ranked by market cap with emission %, price, volume |
| Miners | /subnet/{netuid}/miners |
Miner table with incentive, stake, daily emission (alpha + USD) |
| Validators | /subnet/{netuid}/miners then Validators tab |
Validator table with stake, VTrust, dividends, dominance, daily emission |
| Portfolio | / |
Coldkey lookup showing total balance, per-subnet breakdown, daily yield |
GET /api/v1/price/tao
Current TAO/USDT from MEXC. Cached 30s.
GET /api/v1/portfolio/{coldkey}
Cross-subnet portfolio for a coldkey. Returns total balance (TAO + USD), free balance, staked balance, and per-subnet breakdown with alpha balance, TAO equivalent, price, daily yield.
GET /api/v1/miner/{coldkey}/{netuid}
Response format matches the TaoStats /api/miner/ endpoint. Includes coldkey balance, alpha balances across all subnets, hotkey details with emission data, registration status, and mining rank.
GET /api/v1/subnets # All subnets with market cap, emission %, price, volume
GET /api/v1/subnet/{netuid}/info # Subnet hyperparams and pool data
GET /api/v1/subnet/{netuid}/neurons # Paginated neuron list (?page=1&per_page=50)
GET /api/v1/subnet/{netuid}/metagraph # Full metagraph (?refresh=true to bypass cache)
GET /api/v1/subnet/{netuid}/miners # Miners with daily emission (?sort=incentive&order=desc)
GET /api/v1/subnet/{netuid}/validators # Validators with stake, dividends, daily emission
GET /api/v1/neuron/{netuid}/{uid} # Single neuron by UID
GET /api/v1/neuron/coldkey/{coldkey} # All neurons for a coldkey
GET /api/v1/neuron/hotkey/{hotkey} # Neuron by hotkey
GET /api/v1/emissions/{netuid}/{uid}
Emission breakdown: alpha per epoch, alpha per block, TAO per block, daily/monthly estimates in alpha, TAO, and USD.
GET /api/v1/history/{netuid}/price?hours=720 # Alpha price history (last 30 days)
GET /api/v1/history/{netuid}/snapshots?hours=168 # Full snapshots (last 7 days)
GET /api/v1/history/{netuid}/stats # Data coverage stats
Historical data is stored in SQLite. The backfill script queries the public archive node (wss://archive.chain.opentensor.ai) at epoch-level resolution (~30 min intervals). The live poller adds new snapshots every 30 minutes going forward.
# Backfill last 30 days at epoch resolution (~600 snapshots per subnet)
python -m scripts.backfill --netuid 51 --days 30
# Backfill from a specific block
python -m scripts.backfill --netuid 51 --start-block 5000000
# Resume from where you left off
python -m scripts.backfill --netuid 51 --resume
# Include metagraph data (stake, emissions, neuron count — slower)
python -m scripts.backfill --netuid 51 --days 7 --full# TAO price
curl http://localhost:8000/api/v1/price/tao
# Portfolio for a coldkey
curl http://localhost:8000/api/v1/portfolio/5EhrSbeGeiLgsXcJTXXaBCcqrrMubvWcykSwk4Ho6KUd5sQG
# All subnets ranked by market cap
curl http://localhost:8000/api/v1/subnets
# Subnet 51 miners sorted by incentive
curl "http://localhost:8000/api/v1/subnet/51/miners?sort=incentive&order=desc"
# Subnet 51 validators sorted by stake
curl "http://localhost:8000/api/v1/subnet/51/validators?sort=stake&order=desc"
# Miner info (TaoStats-compatible format)
curl http://localhost:8000/api/v1/miner/5GEP69yPWi3qB2tLQdsbv3Fa2JA6wH6szFNP77EqXizEufvM/51
# Emission breakdown for subnet 51, UID 40
curl http://localhost:8000/api/v1/emissions/51/40
# Historical alpha price for subnet 51 (last 30 days)
curl "http://localhost:8000/api/v1/history/51/price?hours=720"
# Historical data coverage stats
curl http://localhost:8000/api/v1/history/51/statsimport httpx
BASE = "http://localhost:8000/api/v1"
# Portfolio
r = httpx.get(f"{BASE}/portfolio/5EhrSbeGeiLgsXcJTXXaBCcqrrMubvWcykSwk4Ho6KUd5sQG")
p = r.json()
print(f"Balance: {p['total_balance_tao']:.4f} TAO (${p['total_balance_usd']:.2f})")
for sn in p["subnets"]:
print(f" SN{sn['netuid']} {sn['name']}: {sn['balance_tao']:.4f} TAO yield {sn['daily_yield_tao']:.4f}/day")
# Miner data (TaoStats-compatible)
r = httpx.get(f"{BASE}/miner/5GEP69yPWi3qB2tLQdsbv3Fa2JA6wH6szFNP77EqXizEufvM/51")
data = r.json()["data"][0]
print(f"Total balance: {int(data['total_balance']) / 1e9:.4f} TAO")
for hk in data["hotkeys"]:
print(f" UID {hk['uid']} rank #{hk['miner_rank']} emission {int(hk['emission']) / 1e9:.4f} alpha/epoch")
# Emissions
r = httpx.get(f"{BASE}/emissions/51/40")
em = r.json()
print(f"Daily: {em['daily_tao']:.4f} TAO (${em['daily_usd']:.2f})")All settings via environment variables (or .env file):
| Variable | Default | Description |
|---|---|---|
BITTENSOR_NETWORK |
finney |
Network: finney, testnet, local |
SUBTENSOR_ENDPOINT |
(empty) | Custom subtensor websocket URL (e.g. ws://localhost:9944). Overrides BITTENSOR_NETWORK when set. Use this to connect to your own node and avoid public RPC rate limits. |
CACHE_TTL_METAGRAPH |
300 |
Metagraph cache seconds |
CACHE_TTL_PRICE |
30 |
Price cache seconds |
CACHE_TTL_DYNAMIC_INFO |
120 |
Subnet pool data cache seconds |
CACHE_TTL_BALANCE |
60 |
Balance/stake cache seconds |
ARCHIVE_ENDPOINT |
wss://archive.chain.opentensor.ai:443/ |
Archive node for historical backfill |
DATABASE_PATH |
data/opentao.db |
SQLite database path for historical data |
HISTORY_POLL_INTERVAL |
1800 |
Seconds between live snapshots (0 to disable) |
HISTORY_POLL_NETUIDS |
(empty) | Comma-separated netuids to poll (empty = all active) |
API_HOST |
0.0.0.0 |
Bind address |
API_PORT |
8000 |
Port |
OpenTaoAPI/
├── api/
│ ├── main.py # FastAPI app, routes, static file serving
│ ├── config.py # Settings from environment
│ ├── routes/
│ │ ├── price.py # TAO price from MEXC
│ │ ├── miner.py # TaoStats-compatible miner endpoint
│ │ ├── neuron.py # Neuron lookup by UID/hotkey/coldkey
│ │ ├── subnet.py # Subnet info, metagraph, miners, validators
│ │ ├── emissions.py # Emission breakdown
│ │ ├── portfolio.py # Cross-subnet portfolio
│ │ └── history.py # Historical data endpoints
│ ├── services/
│ │ ├── chain_client.py # Bittensor SDK wrapper (AsyncSubtensor)
│ │ ├── price_client.py # MEXC price feed
│ │ ├── cache.py # In-memory TTL cache
│ │ ├── database.py # SQLite storage for historical data
│ │ ├── metagraph_compat.py # SDK version compatibility layer
│ │ └── calculations.py # Emission math
│ └── models/
│ └── schemas.py # Pydantic response models
├── scripts/
│ ├── backfill.py # Historical scraper (chain-direct)
│ └── backfill_taostats.py # Historical scraper (TaoStats API)
├── data/
│ └── opentao.db # SQLite database (created on first run)
├── frontend/
│ ├── index.html # Portfolio dashboard
│ ├── subnets.html # Subnets overview
│ └── miners.html # Miners/validators table
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
└── .env.example
Data sources:
- Bittensor chain via
AsyncSubtensorfor metagraph, balances, stake info, subnet data - MEXC public API for TAO/USDT price (no auth required, 500 req/10s limit)
Emission calculation:
alpha_per_day = meta.E[uid] / tempo * 7200
tao_per_day = alpha_per_day * (pool.tao_in / pool.alpha_in)
usd_per_day = tao_per_day * tao_price
Where meta.E[uid] is alpha per epoch, tempo is blocks per epoch (usually 360), and 7200 is blocks per day.
Validator yield is proportional to stake share: yield = emission * (my_stake / total_stake_on_hotkey).
Caching: metagraph syncs are expensive (~10-20s cold). All queries are cached in-memory with configurable TTLs. Use ?refresh=true on metagraph endpoints to force a fresh sync.
| Feature | TaoStats | OpenTaoAPI |
|---|---|---|
| Rate limit | 5 req/min (free) | None on API; RPC-limited by subtensor node (use your own node for unlimited) |
| API key required | Yes | No |
| Source code | Closed | MIT open source |
| Self-hostable | No | Yes |
| Miner endpoint | /api/miner/{coldkey}/{netuid} |
/api/v1/miner/{coldkey}/{netuid} (compatible format) |
| Web UI | Full explorer | Portfolio, subnets, miners/validators |
| Historical data | Yes | Yes (SQLite, epoch-resolution via archive node) |
| Price source | Multiple | MEXC |
If this project is useful to you, consider supporting development:
TAO: 5EhrSbeGeiLgsXcJTXXaBCcqrrMubvWcykSwk4Ho6KUd5sQG
MIT