New: unified TypeScript, Python & Rust SDKs — available now in beta.

Feeds channel

Subscribe to the real-time WebSocket stream of scored feed items.

The feeds channel pushes scored news and filings the moment they're scored — the same items you'd poll from GET /v1/feeds, delivered in real time over WebSocket.

Connect

Authentication happens on the connection itself — pass your API key as an api_key query parameter (browsers can't set WebSocket headers):

wss://ws.forecite.dev/?api_key=fc_live_your_key_here

Non-browser clients may instead send an Authorization: Bearer fc_live_... header on the upgrade. On a successful connection the server sends a welcome frame with your plan tier and limits:

{ "type": "welcome", "tier": "quant", "limits": { "realtime_feeds_per_day": null, "scored_feed": true } }

Subscribe

Send a subscribe frame to start receiving feeds. snapshot (optional) replays the latest N matching items before the live stream begins:

{
  "type": "subscribe",
  "filters": { "symbols": ["NVDA", "TSLA"], "actionability": true },
  "snapshot": 10
}

Filters accept the same axes as GET /v1/feeds: symbols, exchanges, aggregators, providers, actionability, sentiment_min / sentiment_max, and tags. Tag filtering uses a tags object — one key per dimension mapping to allowed values (OR within a dimension, AND across dimensions):

{
  "type": "subscribe",
  "filters": { "tags": { "market_country": ["US"], "corp_activity": ["earnings", "ma"] } }
}

The server acknowledges with { "type": "subscribed", "filters": { ... } }.

Messages

Each scored item arrives as a feed frame. snapshot is true for replayed history and false for live items; count is your running daily total. data has the same shape as a REST feed item:

{
  "type": "feed",
  "snapshot": false,
  "count": 42,
  "data": {
    "id": "0b3f...",
    "title": "Q3 EPS $4.93 vs $4.59 est; FY guide raised",
    "source": "globenewswire",
    "published_at": "2026-06-30T13:59:00Z",
    "scoring": { "actionability": true, "actionability_score": 82, "sentiment_score": 8 },
    "symbols": [{ "symbol": "NVDA", "exchange": "NASDAQ", "short_symbol": "NVDA" }]
  }
}

On the Free plan the stream still delivers up to 50 items/day, but Verdict scores are withheld — the scoring object is locked:

"scoring": {
  "locked": true,
  "message": "Upgrade to a paid plan to unlock Verdict Engine actionability & sentiment scores.",
  "actionability": null,
  "actionability_score": null,
  "sentiment_score": null,
  "sentiment_short": null,
  "sentiment_conviction": null
}

Paid plans return "locked": false with the real scores. Check scoring.locked before reading scoring.actionability / sentiment_*.

If you hit your plan's daily feed cap, the server sends a quota_exceeded frame and stops delivering until the UTC-day reset:

{ "type": "quota_exceeded", "limit": 5000, "message": "Daily realtime feed limit of 5000 reached for tier starter" }

Heartbeats & keepalive

The server sends WebSocket pings and expects pong replies (handled automatically by most clients). You can also send { "type": "ping" } and receive { "type": "pong" }. Reconnect with backoff on disconnect.

Use the SDK

The official SDKs wrap all of this — connection, auth, subscribe, and reconnect — behind a single stream() call.

On this page