Documentation / New API reference / Subnets
Get Subnets Metagraph History
Reference
Last reviewed 2026-10-07
Per-neuron snapshots over time.
Try it
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
netuid | query | integer (int32) | Yes | Subnet ID (required). |
uid | query | integer (int32) | Filter by neuron UID. | |
block_start | query | integer (int32) | Block range start (inclusive). | |
block_end | query | integer (int32) | Block range end (inclusive). | |
timestamp_start | query | integer (int64) | Timestamp range start, Unix seconds (inclusive). | |
timestamp_end | query | integer (int64) | Timestamp range end, Unix seconds (inclusive). | |
has_incentive | query | boolean | Keep only neurons with positive incentive (false: only neurons with none). The same test as /v1/subnets/metagraph's has_incentive: the served, weighted incentive. | |
page | query | integer (int32) | Page number (1-indexed). Default: 1. Bounded window: (page − 1) × limit must be below 1,000,000. Deep OFFSET paging is linear in the offset, so an uncapped page lets one request scan tens of millions of rows; a page past the window is rejected with a 400. Reach deeper history with this endpoint's range filters (block_start/block_end, timestamp_start/timestamp_end) rather than a larger page. | |
limit | query | integer (int32) | Results per page. Default: 50. Max: 200. | |
order_by | query | string | Column to order by. Default: timestamp. One of timestamp. | |
order_dir | query | string | Sort direction. Default: desc. One of asc, desc. |
Responses
200 — Per-neuron metagraph snapshots over time
| Field | Type | Required | Description |
|---|---|---|---|
data | array | Yes | |
data[].active | boolean | Yes | Whether the neuron is active. |
data[].alpha_stake | string | Yes | Alpha stake (RAO, u64 decimal string): the chain's inherited alpha stake at the row's block, the snapshot row's alpha_stake. |
data[].axon | string, nullable | Yes | Axon ip:port (null when no axon is set). |
data[].block_number | integer (int32) | Yes | Block height of the snapshot. |
data[].coldkey | string | Yes | Coldkey (SS58). |
data[].collateral_earned_alpha | string | Yes | Emission earned while this collateral entry has existed (alpha-RAO decimal string) — the progress meter against the lock, since release is drain_ratio × emission. Not a lifetime-of-hotkey total. |
data[].consensus | string | Yes | Consensus score (normalised u16 / 65535). |
data[].daily_burned_alpha | string, nullable | Yes | Daily burned alpha (RAO decimal string; null when not applicable). |
data[].daily_burned_alpha_as_tao | string, nullable | Yes | Daily burned alpha as TAO (RAO decimal string; null when not applicable). |
data[].daily_mining_alpha | string, nullable | Yes | Daily mining alpha (RAO decimal string; null when not applicable). |
data[].daily_mining_alpha_as_tao | string, nullable | Yes | Daily mining alpha as TAO (RAO decimal string; null when not applicable). |
data[].daily_mining_tao | string, nullable | Yes | Daily mining TAO (RAO decimal string; null when not applicable). |
data[].daily_owner_alpha | string, nullable | Yes | Daily owner alpha (RAO decimal string; null when not applicable). |
data[].daily_owner_alpha_as_tao | string, nullable | Yes | Daily owner alpha as TAO (RAO decimal string; null when not applicable). |
data[].daily_total_rewards_as_tao | string, nullable | Yes | Daily total rewards as TAO (RAO decimal string; always present). |
data[].daily_validating_alpha | string, nullable | Yes | Daily validating alpha (RAO decimal string; null when not applicable). |
data[].daily_validating_alpha_as_tao | string, nullable | Yes | Daily validating alpha as TAO (RAO decimal string; null when not applicable). |
data[].daily_validating_tao | string, nullable | Yes | Daily validating TAO (RAO decimal string; null when not applicable). |
data[].dividends | string | Yes | Dividends (normalised u16 / 65535). |
data[].emission | string | Yes | Emission (RAO, u64 decimal string). |
data[].free_alpha | string | Yes | The hotkey's own alpha not frozen by collateral: hotkey_alpha - locked_alpha, floored at "0" (alpha-RAO decimal string). Read this, not alpha_stake, as "withdrawable at this UID". Per-position (coldkey, hotkey, netuid) withdrawable lives on /v1/alpha/*. |
data[].hotkey | string | Yes | Hotkey (SS58). |
data[].hotkey_alpha | string | Yes | The hotkey's own alpha on this subnet (alpha-RAO decimal string) — TotalHotkeyAlpha(hotkey, netuid). Not the same quantity as alpha_stake above, which is the chain's inherited stake (own − delegated to children + inherited from parents) after the family-parity adjustment. Collateral is locked inside this pool, so this is the base the split below is taken from: locked_alpha + free_alpha == hotkey_alpha (see locked_alpha for the one flooring exception). "0" on rows written before these columns existed — the value was never recorded there, and "0" on both base and free is what says so. |
data[].in_danger | boolean | Yes | Whether the neuron is in danger of deregistration. |
data[].incentive | string | Yes | Incentive: recall's split-weighted incentive across the subnet's mechanisms, normalised to 0..=1 (#1068); the plain u16 / 65535 on a single-mechanism subnet. |
data[].is_child_key | boolean | Yes | Whether this hotkey is a child key on this subnet. |
data[].is_immune | boolean | Yes | Whether the neuron is within its immunity period. |
data[].is_owner_hotkey | boolean, nullable | Yes | Whether this is the subnet owner's hotkey. Derived from the snapshot's projected daily_owner_alpha, which the indexer sets iff the neuron's hotkey owns the subnet. null on rows written before the projected fields landed (#345), where owner state is genuinely unknown rather than false. |
data[].locked_alpha | string | Yes | Alpha still locked as v435 registration collateral (alpha-RAO decimal string). Part of hotkey_alpha, not additional to it — the lock is a flag on real stake. One chain-side exception: both figures floor independently through the share pool, so a long-bonded position can carry a lock a few rao above its own pool, and free_alpha floors at "0" there rather than the sum holding exactly. "0" for every neuron whose hotkey holds no collateral. Registration locks it only on a subnet with a nonzero CollateralLockShare, but a voluntary add_collateral can lock it on any subnet. |
data[].mech_incentive | array | Yes | Per-mechanism incentive (normalised u16 / 65535, one per mechanism). |
data[].mech_incentive[] | string | Yes | |
data[].mech_updated | array | Yes | Blocks since each mechanism's last update (one element per mechanism). |
data[].mech_updated[] | integer (int32) | Yes | |
data[].min_locked_alpha | string | Yes | The miner-set collateral floor (alpha-RAO decimal string). The drain never releases locked_alpha below it, and below it the chain captures earned incentive into the lock instead of paying it out. "0" when no floor is set. |
data[].miner_rank | integer (int32), nullable | Yes | Miner rank among positive-incentive neurons (null otherwise). Equal incentives share a rank and the next value skips (1, 1, 3), matching recall's rankBy. |
data[].name | string, nullable | Yes | Identity name (from the coldkey's on-chain identity). |
data[].netuid | integer (int32) | Yes | Subnet ID. |
data[].registered_at_block | integer (int32) | Yes | Block at which the neuron registered. |
data[].root_stake | string | Yes | Root stake (RAO, u64 decimal string): the snapshot row's tao_stake. |
data[].root_stake_as_alpha | string | Yes | Root stake as alpha (RAO, u64 decimal string): total_alpha_stake - alpha_stake. |
data[].root_weight | string | Yes | Root weight (decimal string; family-derived, "0" if no family row). |
data[].stake_weight | string, nullable | Yes | Stake weight (normalised u16 / 65535). |
data[].timestamp | string | Yes | ISO 8601 timestamp with millisecond precision. |
data[].total_alpha_stake | string | Yes | Total alpha stake (RAO, u64 decimal string): the snapshot row's total_stake. |
data[].uid | integer (int32) | Yes | Neuron UID. |
data[].updated | integer (int32) | Yes | Blocks since the neuron's last update (block_number - last_update). |
data[].validator_permit | boolean | Yes | Whether the neuron holds a validator permit. |
data[].validator_rank | integer (int32), nullable | Yes | Validator rank (among positive-dividend neurons; null otherwise). |
data[].validator_trust | string | Yes | Validator trust (normalised u16 / 65535). |
pagination | object | Yes | Pagination metadata appended to collection responses. next_page / prev_page are explicitly required+nullable in the OpenAPI spec: per docs/api_standards.md we emit null rather than omitting the field, so the SDK should model them as Option/nullable types that are always present in the response. total_items is the exact number of matching rows on every route (issue #992). It used to stop at 1,000,000, which reported a million transfers when finney holds 7,513,252 and a million events when the real figure is 981,179,110; the cap was removed on 29 Sep 2026 and nothing may reintroduce one — crate::pagination::exact_count_sql is the only count form, and no_capped_count_reaches_the_api_surface in data-debug-scripts fails the build if a capped one comes back. total_pages is still bounded, to crate::pagination::max_reachable_page(per_page), because the page parameter is capped to a 1,000,000-row window: deep OFFSET paging reads every skipped row (24.7M rows / 36.6 GiB / 42 s measured on one endpoint, issue #413). So total_pages is exactly the last page this API will serve and next_page / prev_page stay coherent at the window edge, while total_items tells the caller how many rows there really are. History beyond the window is reached with each endpoint's range filters, not a larger page. |
pagination.current_page | integer (int32) | Yes | 1-indexed page that produced the rows in data. |
pagination.next_page | integer (int32), nullable | Yes | Next page number, or null if this is the last page. |
pagination.per_page | integer (int32) | Yes | Number of items requested per page. |
pagination.prev_page | integer (int32), nullable | Yes | Previous page number, or null if this is the first page. |
pagination.total_items | integer (int64) | Yes | Number of items matching the query across all pages. This is the exact count — every matching row, however many there are. It is not bounded by total_pages, so a listing can legitimately report far more items than total_pages × per_page. |
pagination.total_pages | integer (int32) | Yes | Number of pages at the current per_page that this API will serve: ceil(total_items / per_page), capped at ceil(1000000 / per_page) because the page parameter only reaches into the first 1,000,000 rows. When the cap bites, total_items is still the true total and this is the last page a request can ask for; reach deeper history with the endpoint's range filters instead of a larger page. |
400 — Invalid query parameter
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Plain-english message describing the failure from the caller's perspective. |
status_code | integer (int32) | Yes | HTTP status code (mirrored in the response status line for convenience). |
500 — Internal server error
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Plain-english message describing the failure from the caller's perspective. |
status_code | integer (int32) | Yes | HTTP status code (mirrored in the response status line for convenience). |
Every request needs an Authorization header holding your API key — see Getting started with the Taostats API.