Seven US stock indexes: the S&P 500, S&P MidCap 400 and S&P SmallCap 600, the Nasdaq-100, the Russell 1000 and Russell 2000, and the Dow Jones Industrial Average.

Every constituent list is a full-replication tracking fund's own disclosed holdings, taken either from an SEC Form N-PORT filing or from the holdings file the fund publishes daily, and every response names the fund and the date it came from. A list is published only once a fund's filing reaches us whole: a filing that arrived narrowed is stored but never published, because a partial list would read as an index that had shrunk. Rows that could not be matched to a company we track keep the fund's own name and carry `isLinked: false` rather than being dropped.

Each endpoint accepts an index by slug or by name, so `sp-500`, `S%26P%20500` and `SPX` all address the same list. A spelling that could name two indexes, such as `russell`, returns `404` rather than resolving to one of them.

## /v1/indexes

Returns every covered index with the state of the list published for it. `constituentCount` is 0 and `asOfDate` null while no complete list is held. `forecastable` is false only for the Dow, which publishes no quantitative entry rule.

**Parameters:** none.

```bash
curl "https://api.equibles.com/v1/indexes" \
  -H "Authorization: Bearer eq_your_api_key"
```

```python
import requests
r = requests.get(
    "https://api.equibles.com/v1/indexes",
    headers={"Authorization": "Bearer eq_your_api_key"},
)
print(r.json())
```

```javascript
const res = await fetch("https://api.equibles.com/v1/indexes", {
  headers: { Authorization: "Bearer eq_your_api_key" },
});
console.log(await res.json());
```

Response (trimmed to two of the seven indexes):

```json
{
  "data": [
    {
      "slug": "sp-500",
      "name": "S&P 500",
      "provider": "S&P Dow Jones Indices",
      "forecastable": true,
      "constituentCount": 504,
      "asOfDate": "2026-08-19",
      "source": "Held by iShares Core S&P 500 ETF (IVV) daily basket on 19 Aug 2026",
      "isDaily": true
    },
    {
      "slug": "sp-400",
      "name": "S&P MidCap 400",
      "provider": "S&P Dow Jones Indices",
      "forecastable": true,
      "constituentCount": 400,
      "asOfDate": "2026-08-19",
      "source": "Held by iShares Core S&P Mid-Cap ETF (IJH) daily basket on 19 Aug 2026",
      "isDaily": true
    }
  ]
}
```

## /v1/indexes/{index}

Returns one index's current constituent list, heaviest first. `weight` is the company's share of the index as a fraction, so 0.0721 is 7.21%. `fundShares` and `fundValueUsd` size the tracking fund's position, never the company's shares outstanding or market value. The provenance fields travel with every page, because a constituent list without its source and date is a claim we cannot support.

**Parameters:** `index` (path: slug or name); `limit` (default 100, max 500) and `offset` for paging.

```bash
curl "https://api.equibles.com/v1/indexes/sp-500?limit=3" \
  -H "Authorization: Bearer eq_your_api_key"
```

```python
import requests
r = requests.get(
    "https://api.equibles.com/v1/indexes/sp-500?limit=3",
    headers={"Authorization": "Bearer eq_your_api_key"},
)
print(r.json())
```

```javascript
const res = await fetch("https://api.equibles.com/v1/indexes/sp-500?limit=3", {
  headers: { Authorization: "Bearer eq_your_api_key" },
});
console.log(await res.json());
```

Response:

```json
{
  "slug": "sp-500",
  "name": "S&P 500",
  "asOfDate": "2026-08-19",
  "source": "As held by iShares Core S&P 500 ETF (IVV) daily basket on 19 Aug 2026, from the holdings file the fund publishes on its own site (retrieved 20 Aug 2026).",
  "isDaily": true,
  "constituentCount": 504,
  "unlinkedCount": 2,
  "data": [
    {
      "rank": 1,
      "ticker": "NVDA",
      "name": "Nvidia Corp",
      "isLinked": true,
      "weight": 0.07947375,
      "fundShares": 327009346.0000,
      "fundValueUsd": 71144153315.76
    },
    {
      "rank": 2,
      "ticker": "AAPL",
      "name": "Apple Inc.",
      "isLinked": true,
      "weight": 0.07018269,
      "fundShares": 198298398.0000,
      "fundValueUsd": 62826881438.34
    },
    {
      "rank": 3,
      "ticker": "MSFT",
      "name": "Microsoft Corp",
      "isLinked": true,
      "weight": 0.05426025,
      "fundShares": 100293740.0000,
      "fundValueUsd": 48573261219.40
    }
  ],
  "meta": {
    "limit": 3,
    "offset": 0,
    "count": 3,
    "hasMore": true,
    "total": 504
  }
}
```

## /v1/indexes/{index}/changes

Returns the companies that joined or left an index, newest first. Each change is dated to a window, `previousAsOfDate` to `observedAsOfDate`, rather than to an announcement: a daily holdings file dates a change to a day, a quarterly filing only to a quarter. `sourceCount` says how many tracking funds reported it, because several funds track one index and their records for the same company and direction over overlapping windows are collapsed into one event.

**Parameters:** `index` (path: slug or name); `limit` (default 25, max 500) and `offset` for paging.

```bash
curl "https://api.equibles.com/v1/indexes/nasdaq-100/changes?limit=3" \
  -H "Authorization: Bearer eq_your_api_key"
```

```python
import requests
r = requests.get(
    "https://api.equibles.com/v1/indexes/nasdaq-100/changes?limit=3",
    headers={"Authorization": "Bearer eq_your_api_key"},
)
print(r.json())
```

```javascript
const res = await fetch("https://api.equibles.com/v1/indexes/nasdaq-100/changes?limit=3", {
  headers: { Authorization: "Bearer eq_your_api_key" },
});
console.log(await res.json());
```

Response:

```json
{
  "data": [
    {
      "direction": "Added",
      "name": "Walmart Inc.",
      "ticker": "WMT",
      "isLinked": true,
      "observedAsOfDate": "2026-03-31",
      "previousAsOfDate": "2025-12-31",
      "weight": 0.03438842,
      "sourceCount": 1
    },
    {
      "direction": "Removed",
      "name": "AstraZeneca PLC",
      "ticker": null,
      "isLinked": false,
      "observedAsOfDate": "2026-03-31",
      "previousAsOfDate": "2025-12-31",
      "weight": 0.00295849,
      "sourceCount": 1
    },
    {
      "direction": "Added",
      "name": "Western Digital Corp",
      "ticker": "WDC",
      "isLinked": true,
      "observedAsOfDate": "2025-12-31",
      "previousAsOfDate": "2025-09-30",
      "weight": 0.00324727,
      "sourceCount": 1
    }
  ],
  "meta": {
    "limit": 3,
    "offset": 0,
    "count": 3,
    "hasMore": true
  }
}
```

## /v1/indexes/{index}/forecast

Applies an index's own published entry rules to every company we track, with a rule-by-rule scorecard behind each company.

`kind` settles how the answer should be read. `Screen` means an S&P Dow Jones Indices committee makes the final decision. The S&P 500 supplements that screen with a versioned probability model: `probabilityHorizonDays` gives the fixed horizon, `probabilityModelVersion` identifies the calculation, `probabilitySource` and `probabilitySourceUrl` identify its empirical turnover calibration, and each eligible join candidate and current-member exit row carries `probability` as a fraction from zero to one. Other S&P indexes remain raw screens. `Projection` means a published ranking decides membership, so eligibility is scored and then the ranking is applied.

A rule outcome of `NotEvaluated` is a fact about the company, a figure we could not read, and it blocks an entry verdict. `NotApplied` is a fact about us, a rule we run for nobody, disclosed and excluded from the verdict. `unmeasuredMemberCount` counts members in that state. They stay out of deterministic departure buckets because missing data is not evidence of an exit; the S&P 500 probability model still gives them the common historical exit baseline. For a projection the ranking is built without them.

Each bucket's `count` is public. Candidate identities, directions, ranks, values, and rule evidence require a Plus or Pro plan; Free responses set `candidatesLocked` to `true` and return empty `rows`. The Dow returns `404`: it publishes no quantitative entry rule, so there is nothing to apply. Snapshots are written once per trading day from settled closes and never revised.

**Parameters:** `index` (path: slug or name; the Dow returns 404).

```bash
curl "https://api.equibles.com/v1/indexes/sp-500/forecast" \
  -H "Authorization: Bearer eq_your_api_key"
```

```python
import requests
r = requests.get(
    "https://api.equibles.com/v1/indexes/sp-500/forecast",
    headers={"Authorization": "Bearer eq_your_api_key"},
)
print(r.json())
```

```javascript
const res = await fetch("https://api.equibles.com/v1/indexes/sp-500/forecast", {
  headers: { Authorization: "Bearer eq_your_api_key" },
});
console.log(await res.json());
```

Free responses keep the aggregate counts and probability calibration metadata, set `candidatesLocked` to `true`, provide the pricing `upgradeUrl`, and return empty bucket `rows`.

Plus and Pro responses set `candidatesLocked` to `false`, return `upgradeUrl` as `null`, and populate each bucket's `rows` with the candidate and its rule scorecard. On an S&P 500 probability snapshot, rows are ordered by probability from highest to lowest and the full stored current-member exit distribution is returned. No response example is shown before a production probability snapshot exists.