CHAMBERFEED Console

Developer guide

Stream ChamberFeed court signals straight into your trading and research pipelines.

WebSocket streamPush in milliseconds. One connection, every signal.
WebhooksSigned HTTPS POST to your endpoint for each event.
REST feedHistory, back-fill and catch-up after a disconnect.

Quickstart

Install the Python client and start streaming with your API key:

pip install "chamberfeed-client @ https://chamberfeed.io/static/sdk/chamberfeed_client-0.1.0-py3-none-any.whl"

export CHAMBERFEED_API_KEY=cf_live_...
chamberfeed-stream --tickers CRWD NFLX     # JSON lines on stdout; pipe into Kafka, a file, anything

Or in your own code:

import asyncio
from chamberfeed_client import ChamberFeedClient

async def main():
    client = ChamberFeedClient(api_key="cf_live_...")
    async for event in client.stream(tickers=["CRWD"]):
        print(event.type, event.ticker, event.data)

asyncio.run(main())

Authentication

Machine clients use the API key ChamberFeed issued to you. Send it as a header. Keys are shown once, can be rotated or revoked at any time, and stop working automatically when a trial or licence ends.

X-API-Key: cf_live_...
Never put the key in a URL. The stream refuses ?api_key= and ?token= so credentials can't leak into proxy or CDN logs. Store the key in a secrets manager, not in source control.

Python client

chamberfeed-client (Python 3.10+) wraps the stream with the behaviour every production consumer needs:

from datetime import datetime, timezone
from chamberfeed_client import AccessEnded, AuthError, ChamberFeedClient

client = ChamberFeedClient(api_key=KEY)

# Resume from your own checkpoint after a restart:
async for ev in client.stream(tickers=["CRWD", "NFLX"], since=last_checkpoint):
    if ev.type == "raw" and ev.data.get("signal"):
        trade_signal(ev.ticker, ev.data["signal"], ev.data["confidence"])
    elif ev.type == "enriched":
        update_model(ev.data)            # ticker, motion_type, outcome, sentiment
    elif ev.type == "context":
        attach_note(ev.id, ev.data["kind"], ev.data["text"])
    save_checkpoint(ev.timestamp)

# History / back-fill:
rows = await client.feed(since=datetime(2026, 11, 1, tzinfo=timezone.utc), ticker="CRWD")

WebSocket stream

Endpoint: wss://chamberfeed.io/v1/stream. Available to Tier-1 (real-time) clients and active trials.

1. Connect and authenticate

Send X-API-Key on the upgrade request. (Browsers, which can't set headers, send {"action":"auth","token":"<JWT>"} as the first frame within 5 seconds.) The server replies:

{"type": "welcome", "role": "client_premium", "tickers": null}

2. Optionally filter by ticker

→ {"action": "subscribe", "tickers": ["CRWD", "NFLX"]}      // [] = everything
← {"type": "subscribed", "tickers": ["CRWD", "NFLX"]}

3. Receive events

Every message is JSON with a type. Ignore types you don't recognise; new ones may be added.

import asyncio, json, websockets

async def main():
    async with websockets.connect("wss://chamberfeed.io/v1/stream",
                                  additional_headers={"X-API-Key": KEY}) as ws:
        print(json.loads(await ws.recv()))                     # welcome
        await ws.send(json.dumps({"action": "subscribe", "tickers": ["CRWD"]}))
        async for raw in ws:
            msg = json.loads(raw)
            if msg["type"] in ("raw", "enriched", "context"):
                handle(msg)

asyncio.run(main())
// Node.js (ws package)
const WebSocket = require("ws");
const ws = new WebSocket("wss://chamberfeed.io/v1/stream", { headers: { "X-API-Key": process.env.KEY } });
ws.on("open", () => ws.send(JSON.stringify({ action: "subscribe", tickers: ["CRWD"] })));
ws.on("message", (buf) => { const m = JSON.parse(buf); if (["raw","enriched","context"].includes(m.type)) handle(m); });

Message types

typeWhenWhat it contains
rawInstantly (milliseconds) when a runner taps a ruling button or a scraper detects a change The signal as captured: signal + confidence for one-tap rulings, or free text. Act on this for speed.
enrichedA fraction of a second later Same id, plus ticker, motion_type, outcome, sentiment, confidence.
contextSeconds to minutes later Stage-2 detail linked by event_id: kind = context (dictated notes) or retraction (the signal was sent in error; unwind it).
heartbeatEvery 20 s when idleKeep-alive. No action needed.
access_endedWhen a trial/licence ends or a key is revokedThe server then closes the socket (code 1008). Don't reconnect.

Examples

{"type": "raw", "broadcast_timestamp_utc": "2026-11-09T15:14:02.081Z",
 "event": {"id": "4f1c…", "case_name": "Acme Holdings LLC v. CrowdStrike, Inc.", "ticker": "CRWD",
           "court_jurisdiction": "NY Commercial Div Part 53 (Borrok)", "signal": "MOTION_DENIED",
           "confidence": "HIGH", "motion_type": "motion_to_dismiss",
           "ruling_summary": "Motion to dismiss denied from the bench.",
           "chamberfeed_timestamp": "2026-11-09T15:14:02.003Z", "source": "manual"}}

{"type": "enriched", "event": {"id": "4f1c…", "ticker": "CRWD", "motion_type": "motion_to_dismiss",
 "outcome": "denied", "sentiment": "bearish", "confidence": 0.95, "signal": "MOTION_DENIED", …}}

{"type": "context", "event": {"event_id": "4f1c…", "kind": "context", "ticker": "CRWD",
 "text": "Court found jurisdiction adequately pleaded on count two; expedited discovery ordered.",
 "timestamp": "2026-11-09T15:15:10Z"}}

Field reference

FieldValues
signalMOTION_DENIED, MOTION_GRANTED, MOTION_GRANTED_IN_PART, STAY_LIFTED, INJUNCTION_GRANTED, INJUNCTION_DENIED, DECISION_RESERVED; null for free-text and scraper events
confidence (raw)HIGH (runner certain) or MEDIUM (fairly sure)
confidence (enriched)0–1 (HIGH = 0.95, MEDIUM = 0.7 for one-tap signals)
motion_typemotion_to_dismiss, preliminary_injunction, summary_judgment, motion_to_lift_stay, tro, class_certification, calendar_update, other
outcomegranted, denied, granted_in_part, leaning_grant, leaning_deny, reserved, unknown
sentimentbullish, bearish, neutral, unknown (for the named public company)
sourcemanual (courtroom runner) or scraper (court website change)
chamberfeed_timestampUTC capture time, microsecond precision; the basis of all latency measurements
Treat raw as the fastest directional indicator and enriched as the structured record. Always honour retraction.

Reconnect & catch-up

The Python client does all of this for you. If you write your own consumer:

  1. Reconnect with back-off: 0.5 s, 1 s, 2 s … capped at 30 s, with random jitter.
  2. Track the latest chamberfeed_timestamp you processed.
  3. After reconnecting, call GET /v1/feed?since=<last timestamp − 30 s> and process anything you haven't seen.
  4. De-duplicate on (type, id); for context use (event_id, timestamp).
  5. Read fast. Each connection has a buffer of 1,000 messages. A consumer that falls further behind is disconnected (close code 1013) and should reconnect and catch up. Hand events to a queue rather than doing slow work inside the read loop.
  6. Close code 1008 means access was refused or ended. Don't retry; check your key or contact us.

REST feed

curl -H "X-API-Key: $KEY" "https://chamberfeed.io/v1/feed?since=2026-11-09T14:00:00Z&ticker=CRWD&limit=500"
curl -H "X-API-Key: $KEY" "https://chamberfeed.io/v1/events/<id>"
ParameterMeaning
sinceISO-8601 time; only events captured at or after it
tickerFilter to one ticker
limit1–1000 (default 100); results are newest first

Responses are the enriched records, including context_updates, retracted_at and benchmark fields. Interactive reference: /docs.

Webhooks

Prefer push to your own HTTPS endpoint? Ask us to register a webhook URL. Each enriched event is POSTed as JSON and signed with your webhook secret (shown once when configured). Failed deliveries are retried briefly; use the REST feed to reconcile.

POST /your/endpoint
Content-Type: application/json
X-ChamberFeed-Timestamp: 1762701242
X-ChamberFeed-Signature: sha256=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>
# Python
from chamberfeed_client import verify_webhook

ok = verify_webhook(raw_body_bytes, WEBHOOK_SECRET,
                    headers["X-ChamberFeed-Timestamp"], headers["X-ChamberFeed-Signature"])
if not ok:
    return 401
// Node.js
const crypto = require("crypto");
function verify(rawBody, secret, ts, sig) {
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;           // reject replays
  const mac = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return sig.length === mac.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(mac));
}
Verify against the raw request body, before any JSON parsing, and reject timestamps older than 5 minutes.

Access & limits

Compliance teams: see the data sourcing & compliance statement. Questions or a key rotation? Contact your ChamberFeed representative.