# Get Validators Baskets History

Daily beta-basket history.

_Source: https://taostats.io/docs/new/validators/get-validators-baskets-history_

_Last reviewed: 2026-10-19_

```http
GET https://api.taostats.io/v1/validators/baskets/history
```

Requires an API key in the `Authorization` header.

Daily beta-basket history.

## Try it

Try this request in the browser on the HTML version of this page, or use the code samples below.

### Code samples

**cURL**

```bash
curl -H "Authorization: <YOUR_API_KEY>" \
  "https://api.taostats.io/v1/validators/baskets/history"
```

**JavaScript**

```js
const response = await fetch('https://api.taostats.io/v1/validators/baskets/history', {
  headers: {
    Authorization: '<YOUR_API_KEY>',
  },
});
const data = await response.json();
```

**Python**

```python
import requests

response = requests.get(
    "https://api.taostats.io/v1/validators/baskets/history",
    headers={"Authorization": "<YOUR_API_KEY>"},
)
data = response.json()
```

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `hotkey` | query | `string` |  | Filter to one validator hotkey (SS58 or `0x`-hex). |
| `timestamp_start` | query | `integer (int64)` |  | Range start, unix seconds (inclusive). Resolved to the **UTC day** the instant falls in, since rows are one-per-day. |
| `timestamp_end` | query | `integer (int64)` |  | Range end, unix seconds (inclusive). Resolved to the **UTC day** the instant falls in, so the whole of that day is included. |
| `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: `day`. One of `day`, `nav_tao`, `spot_nav_tao`, `shares`, `nav_per_share`, `performance`, `return_7d`, `return_30d`, `staker_return_7d`, `staker_return_30d`. |
| `order_dir` | query | `string` |  | Sort direction. Default: `desc`. One of `asc`, `desc`. |

## Responses

### `200` — Daily beta-basket history

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | `array` | Yes |  |
| `data[].block_number` | `integer (int32)` | Yes | Block the snapshot was read at. |
| `data[].day` | `string` | Yes | UTC date of `timestamp` (`YYYY-MM-DD`). On the history endpoint it is the row's identity — the day whose closing block this snapshot is. |
| `data[].deposited_tao` | `string` | Yes | Lifetime realizable TAO deposited into the fund (RAO, u64 as decimal string). |
| `data[].holdings` | `array` | Yes | Per-subnet holdings, each valued at spot and realizable. |
| `data[].holdings[].alpha` | `string` | Yes | Alpha held (RAO, u64 as decimal string). |
| `data[].holdings[].netuid` | `integer (int32)` | Yes | Subnet ID. |
| `data[].holdings[].realizable_tao` | `string` | Yes | Slippage-aware redemption quote net of fees (RAO, u64 as decimal string). This, not `spot_tao`, is what `nav_tao` is the sum of. |
| `data[].holdings[].spot_tao` | `string` | Yes | Spot mark, `price × alpha` (RAO, u64 as decimal string). **Display only** — the chain itself says spot "is exposed for display/analytics only and is never used for share pricing or redemption sizing". |
| `data[].hotkey` | `string` | Yes | Validator hotkey (SS58). |
| `data[].nav_per_share` | `number (double), nullable` | Yes | `nav_tao / shares`. **`null` when the fund has no shares outstanding** — no supply means no share price, and `0.0` would read as "worthless". |
| `data[].nav_tao` | `string` | Yes | **Realizable** (slippage-aware) NAV — the fund's redemption value, and the basis of every derived figure here (RAO, u64 as decimal string). |
| `data[].performance` | `number (double), nullable` | Yes | `(nav_tao + redeemed_tao) / deposited_tao` — the lifetime multiple, 1.0 is break-even. `null` when nothing was ever deposited. |
| `data[].rate` | `number (double)` | Yes | `SubtensorModule::BasketRate` — cumulative fund shares accrued per unit of root stake. **Never null**: the chain declares the map `ValueQuery` with a zero default, so an absent key is a real `0`. |
| `data[].redeemed_tao` | `string` | Yes | Lifetime TAO redeemed out of the fund (RAO, u64 as decimal string). |
| `data[].return_30d` | `number (double), nullable` | Yes | As `return_7d`, over 30 days. |
| `data[].return_7d` | `number (double), nullable` | Yes | Capital return on `nav_per_share` over 7 days — **not annualised**, and legitimately negative. `null` until the window has an anchor. |
| `data[].shares` | `string` | Yes | Outstanding fund shares (u64 as decimal string). |
| `data[].spot_nav_tao` | `string` | Yes | Spot-marked NAV, `Σ price × alpha` (RAO, u64 as decimal string) — the headline mark only. |
| `data[].staker_return_30d` | `number (double), nullable` | Yes | As `staker_return_7d`, over 30 days. |
| `data[].staker_return_7d` | `number (double), nullable` | Yes | Staker total return over 7 days, `twr / twr_then − 1` — income and mark-to-market together, **not annualised**, defined only within one fund life. `null` when either sample is missing or the two are from different lives. |
| `data[].timestamp` | `string` | Yes | ISO 8601 timestamp with millisecond precision. |
| `data[].twr` | `number (double), nullable` | Yes | `SubtensorModule::BasketTwr` — the fund's staker total-return accumulator, 1.0 at each fund life's baseline. `null` on a runtime before release 450, which introduced it. |
| `data[].twr_first_block` | `string, nullable` | Yes | `BetaBaseline.first_block` — the block this fund's **current life** was stamped at (u64 as decimal string). Two `twr` samples are comparable only within one life. `null` pre-450 and for a fund the chain calls *provisional* (shares outstanding, no baseline stamped) — never `0`, which is a block number. |
| `data[].weights` | `array` | Yes | The validator's root weight vector as stored by the chain. It has been empty for every validator since block 9,088,597, when runtime 464 removed root weights. An empty array does not mean the validator's stakers miss rewards. |
| `data[].weights[].netuid` | `integer (int32)` | Yes | Subnet ID. |
| `data[].weights[].weight` | `integer (int32)` | Yes | Raw chain weight. |
| `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](https://taostats.io/docs/start-here/getting-started-with-taostats-api).
