> ## 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 Market Data Stream API

> Admin controls for the Cloudflare market-data-stream Worker. Browser stream tickets are minted directly by the Worker.

The `marketDataStream.*` namespace is intentionally small. Public browser pages
connect to `apps/market-data-stream` directly and call its public
`POST /v1/tickets` endpoint to mint short-lived scoped stream tokens. Platform
API stays out of the realtime hot path and only proxies privileged admin/status
operations to the Worker.

* Base endpoint: `POST /v1/trpc`
* Namespace: `marketDataStream.*`
* Admin auth: platform-api admin bearer token
* Worker auth: platform-api admin procedures call the Worker with
  `x-market-data-stream-internal-token`

## Public Worker Ticket Route

Browser clients call the Worker directly:

```text theme={null}
POST /v1/tickets
```

The request accepts a structured public target, not arbitrary topic keys or
provider identifiers:

* Equity asset detail: `{ kind: "asset", assetClass: "equity", symbol,
  collectionFeeds }`
* Crypto asset detail: `{ kind: "asset", assetClass: "crypto", symbol,
  collectionFeeds }`
* Stocks/watchlist collection: `{ kind: "collection", assetClass: "equity",
  collection, symbols }`
* Crypto collection: `{ kind: "collection", assetClass: "crypto",
  collection: "crypto", symbols }`

The Worker resolves provider subscription identifiers from its internal public
asset registry, derives allowed topics, registers provider subscriptions against
Durable Objects, applies IP-based ticket rate limiting, and returns:

```json theme={null}
{
  "baseUrl": "https://market-data-stream-staging.example.workers.dev",
  "expiresInSeconds": 300,
  "token": "...",
  "topics": ["asset:equity:TSLA:ohlcv:1s"]
}
```

Browsers then connect to the returned Worker base URL:

* `GET /v1/assets/:assetClass/:symbol/:feedKind/:resolution/snapshot`
* `GET /v1/assets/:assetClass/:symbol/:feedKind/:resolution/ws`
* `GET /v1/collections/:namespace/:name/snapshot`
* `GET /v1/collections/:namespace/:name/ws`

The scoped token is supplied as `?token=...`. Resume windows use
`?afterSequence=<last-seen-sequence>`.

The public asset registry is seeded through the Worker internal route:

```text theme={null}
POST /internal/public-registry/assets/upsert
```

Example body:

```json theme={null}
{
  "assets": [
    { "assetClass": "equity", "provider": "massive", "symbol": "TSLA" },
    {
      "assetClass": "crypto",
      "provider": "coingecko",
      "symbol": "AAVE",
      "coingeckoId": "aave"
    }
  ]
}
```

Public ticket requests fail closed with `403 public_asset_not_allowed` until the
requested symbols are present in this Worker-side registry.

## Admin Procedures

* `marketDataStream.adminStatus`
  * Purpose: fetch provider status, capability registry, subscription counts,
    reconnect state, per-topic client counts, replay windows, and last event
    timestamps from the Worker
  * Auth: admin bearer token

* `marketDataStream.adminConnectProvider`
  * Purpose: manually connect Massive stocks or manually poll CoinGecko quotes
  * Auth: admin bearer token
  * Input: `{ provider: "massive" | "coingecko" }`

* `marketDataStream.adminDisconnectProvider`
  * Purpose: manually disconnect the Massive stock WebSocket. CoinGecko polling
    has no persistent socket, so disconnect returns a no-op reason.
  * Auth: admin bearer token
  * Input: `{ provider: "massive" | "coingecko" }`

## Topic Contract

Asset feeds use:

```text theme={null}
asset:{assetClass}:{SYMBOL}:{feedKind}:{resolution}
```

Collection feeds use:

```text theme={null}
collection:{namespace}:{name}
```

Initial browser wiring maps canonical asset metadata to topics:

* canonical equity or fund underlyings such as TSLA:
  `asset:equity:TSLA:ohlcv:1s`, provider `massive`
* canonical crypto underlyings with `metadata.coingeckoId`, such as AAVE:
  `asset:crypto:AAVE:quote:tick`, provider `coingecko`
* stocks page collection: `collection:stocks:default`
* crypto page collection: `collection:crypto:default`
* dashboard watchlist collection: `collection:watchlist:default`

## Runtime Configuration

Platform API reads:

* `MARKET_DATA_STREAM_BASE_URL`: public Worker URL used for admin proxy calls
* `MARKET_DATA_STREAM_INTERNAL_BASE_URL`: optional internal Worker URL for
  platform-api-to-Worker calls; defaults to the public base URL
* `MARKET_DATA_STREAM_INTERNAL_TOKEN`: shared internal token for Worker
  `/internal/*` routes

`apps/webapp-v2` reads:

* `VITE_MARKET_DATA_STREAM_URL`: public Worker URL used by anonymous and
  authenticated browser clients for `POST /v1/tickets`, snapshots, and
  WebSockets

The Worker keeps `MARKET_DATA_STREAM_PROVIDER_CONNECT_ENABLED=false` in staging
until Massive redistribution entitlement is confirmed. CoinGecko polling is
also default-disabled with `MARKET_DATA_STREAM_COINGECKO_POLL_ENABLED=false`
and can be manually burn-in tested through the admin poll path.
