> ## Documentation Index
> Fetch the complete documentation index at: https://docs.glider.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# tRPC Direct Index API

> Universe catalog and constituent serving for the direct index builder.

The `directIndex.*` namespace serves the webapp-v2 direct index builder: the
catalog of investable universes (real indices and equity sectors) and the
resolved constituent lists the builder edits against.

* Base endpoint: `POST /v1/trpc`
* Namespace: `directIndex.*`
* Auth: public (rate-limited, Tier 2)
* Primary model: CANONICAL equities. Every constituent is a canonical
  underlying (e.g. `TSLA`) identified by `assetCanonicalId` and displayed by
  ticker — never a tokenized instrument (`TSLAon`). Instrument resolution
  (issuer + chain) is performed server-side by a central selection policy and
  surfaced as `primaryInstrument` per constituent.

## Procedures

* `directIndex.getCatalog`
  * Purpose: the grouped universe picker (Popular / Sectors / Broad Market /
    All) shown when starting a direct index
  * Input: none
  * Response: array of groups, each with:
    * `key`: `popular`, `sectors`, `industries`, `thematic`, `broad_market`,
      or `all`
    * `label`: display label
    * `entries[]`: universe references —
      * `kind`: `index` (registry-backed, e.g. SPY holdings) or `sector`
        (display-sector taxonomy)
      * `id`: `core.asset_indices.asset_index_id` uuid for indices, or the
        sector category value (e.g. `financials`) for sectors
      * `symbol`: index symbol when `kind = index`
      * `name`: display name
      * `memberCount`: members in the latest snapshot; `null` when not
        cheaply countable (sector universes)
      * `methodologyUrl`: provider methodology link when known
  * Notes: indices without weighted members in their latest snapshot (e.g.
    smoke/test rows) are filtered out; empty groups are omitted.

* `directIndex.getConstituents`
  * Purpose: the resolved member list for a chosen universe — the raw input
    the builder applies top-N, weighting, and exclusions to
  * Input:
    * required `universe.kind`: `index` or `sector`
    * required `universe.id`: index uuid or sector category value
  * Response:
    * `universe`: resolved universe reference (server-authoritative name)
    * `snapshotAt`: ISO timestamp the constituent snapshot was resolved
    * `resolvedChainId`: the single EVM chain the instrument resolution
      targeted (strategies are single-chain); constituents without an
      instrument on this chain are marked `investable: false`
    * `constituents[]`, ranked (index weight desc for indices, market cap
      desc for sectors):
      * `assetCanonicalId`, `ticker`, `name`
      * `sector`: current display-sector classification, or `null`
      * `indexWeightBps`: latest index weight in basis points (`null` for
        sector universes)
      * `marketCapUsd`: decimal string (`null` when unknown)
      * `rank`: 1-based position in the universe ranking
      * `investable`: whether a glider-supported tokenized instrument exists
        on `resolvedChainId`
      * `primaryInstrument`: `{ assetId, chainId, issuerSlug, tokenSymbol }`
        or `null`; `assetId` is the internal `address:chainId` form used by
        strategy asset blocks
  * Errors:
    * `DIRECT_INDEX_NOT_FOUND` (404): unknown index id or sector value
    * `DIRECT_INDEX_EMPTY_UNIVERSE` (404): universe has no current members
    * `DIRECT_INDEX_UNAVAILABLE` (500): serving dependencies unavailable

* `directIndex.getMetrics`
  * Purpose: bulk market-data overlay for the positions table (price, 1D
    change, 1Y change) — the client joins it to constituents by ticker
  * Input: required `tickers` (1-300 canonical tickers)
  * Response: `{ metrics: [{ ticker, priceUsd, change1DPct, change1YPct }],
    yearAgoDate }` — `yearAgoDate` is the trading day used as the 1Y baseline
  * Implementation: exactly two upstream shapes regardless of ticker count —
    chunked multi-ticker snapshots plus ONE grouped-daily aggregate for the
    \~1-year-ago trading day (cached per calendar day, holiday walk-back).
    Unknown tickers degrade to nulls; the batch never fails per-ticker.

* `directIndex.getFundamentals`
  * Purpose: fundamentals overlay (P/E, dividend yield) — per-ticker upstream
    cost, so clients page requests in chunks while the table fills
    progressively
  * Input: required `tickers` (1-25)
  * Response: `{ summaries: [{ ticker, priceToEarnings, dividendYield,
    earningsPerShare, marketCap }] }`
  * Each underlying fundamentals lookup is cached (\~3 h TTL); missing
    fundamentals return null fields rather than failing the batch.

## Semantics worth knowing

* **Index membership is snapshot-layered.** Ingestion appends a new batch of
  membership rows per sync; "current" always means the latest snapshot. The
  S\&P 500 universe contains only holdings resolvable to Glider canonicals
  (\~225 names, \~84% of index weight), which is ample for top-N ≤ 20 building.
* **Instrument selection policy** (v1): glider-supported issuer products
  only; single EVM chain chosen to maximize universe coverage (priority
  order `1`, then `56`); issuer ties broken by configured priority. The
  policy is centralized server-side so future multi-issuer support does not
  change this API's shape.
