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

# Stream factor changes

> Subscribe to factor deltas via WebSocket and update a model in real time.

You run a model that takes Alterscope factors as inputs. Polling the REST endpoint every minute wastes quota and lags reality. The WebSocket factor stream pushes a row every time a factor changes for the opportunities you subscribe to.

## What you build

A long-lived WebSocket consumer that keeps your in-memory factor cache fresh.

## 1. Open the connection

<CodeGroup>
  ```python python theme={null}
  import asyncio
  import websockets
  import json
  import os

  async def consume():
      # Subscription is set via query params on connect: opportunities to watch,
      # categories to filter by, an optional alert_threshold, and details=true.
      url = (
          "wss://api.alterscope.org/v2/ws/factors"
          "?opportunities=hyperliquid-btc-basis,aave-usdc-lending"
          "&categories=liquidity,oracle"
          "&details=true"
      )
      headers = {"Authorization": f"Bearer {os.environ['ALTERSCOPE_API_KEY']}"}
      async with websockets.connect(url, additional_headers=headers) as ws:
          async for raw in ws:
              event = json.loads(raw)
              apply_factor_update(event)

  asyncio.run(consume())
  ```

  ```javascript javascript theme={null}
  import WebSocket from "ws";

  // Subscription is set via query params on connect.
  const url =
    "wss://api.alterscope.org/v2/ws/factors" +
    "?opportunities=hyperliquid-btc-basis" +
    "&categories=liquidity,oracle" +
    "&details=true";

  const ws = new WebSocket(url, {
    headers: { Authorization: `Bearer ${process.env.ALTERSCOPE_API_KEY}` }
  });

  ws.on("message", raw => {
    const event = JSON.parse(raw);
    applyFactorUpdate(event);
  });
  ```
</CodeGroup>

## 2. Sample message

```json theme={null}
{
  "type": "factor.changed",
  "opportunity_id": "hyperliquid-btc-basis",
  "factor": "dynamic_stability_ratio",
  "previous": 0.74,
  "current": 0.61,
  "as_of_block": 22341488,
  "as_of": "2026-04-27T08:34:02Z"
}
```

## 3. Reconnect logic

The server pings every 30 seconds. If you miss two pings, reconnect with exponential backoff (start at 1s, cap at 30s). Re-open the connection with the same query params on each reconnect — the subscription lives in the URL. The server does not replay messages on reconnect; if you need gap-fill, run a single REST query for the affected opportunities right after reconnect.

## 4. Tier requirements

WebSocket streaming requires the Analyst tier or higher. The Free tier has no live stream.

## 5. Troubleshooting

* **403 on connect**: verify the key has `read:yield:detail` scope and your org is on the Analyst tier or higher.
* **No messages**: the stream is event-driven. Quiet markets produce quiet streams. Narrow or widen your `categories` filter (`liquidity`, `oracle`, `governance`, `economic`) if you expected traffic and saw none.
