# Stream: wss://stream.novigapi.com/v1/stream

> Independent. Not affiliated with, endorsed by or sponsored by Novig.

Canonical: https://novigapi.com/docs/endpoints/stream
Facts checked: 2026-11-12

The novigapi.com stream is a WebSocket that pushes Novig price changes, trades, line moves and steam within 5 to 30 seconds, by league, event, player or market. Free connections are delayed 300 seconds; paid plans are live. Novig's own WebSocket needs a signed trading key from an account, checked 12 Nov 2026. (Source: [Novig docs: Streaming connection](https://docs.novig.com/api/streaming/connection.md))

## What it returns

One connection, up to 50 subscriptions. Instead of polling every market, subscribe to what you care about and get an `event` message each time a price changes (`price.changed`), a fill prints (`trade`), a line moves (`line.move`) or steam hits (`steam`). Each subscription takes one filter: `league`, `event_id`, `player` or `market_id`, plus an optional `types` list.

It is near-live, not tick-level: our collector reads Novig's public catalog every 5 to 15 seconds for active markets, and changes reach you within 5 to 30 seconds.

Stream limits by plan

| Plan | Connections | Delay |
| --- | --- | --- |
| Free | 1 | 300 s |
| Developer | 3 | Live |
| Production | 10 | Live |
| Scale | 15 | Live |


## Parameters

Query and path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| Authorization | header | Yes | `Bearer nv_...` on the upgrade request. |
| key | string (query) | No | The key as `?key=nv_...`, for browsers, which cannot set headers on a WebSocket. Keep such a key out of public pages. |
| op | string (message) | Yes | `subscribe`, `unsubscribe` or `ping`. |
| league, event_id, player, market_id | string (message) | One of them | The filter for a `subscribe`. One per subscription; send several subscribes for more. |
| types | string[] (message) | No | Only these event types: `price.changed`, `trade`, `line.move`, `steam`. Default: all. |
| id, all | string, boolean (message) | For unsubscribe | `{"op":"unsubscribe","id":"<subscription id>"}` or `{"op":"unsubscribe","all":true}`. |


## Request

```bash
# Node: npm install ws
node -e '
const WebSocket = require("ws");
const ws = new WebSocket("wss://stream.novigapi.com/v1/stream", { headers: { Authorization: "Bearer nv_..." } });
ws.on("open", () => ws.send(JSON.stringify({ op: "subscribe", player: "cooper-flagg" })));
ws.on("message", (m) => console.log(m.toString()));
'
```

## Response

A `price.changed` event for Cooper Flagg Over 1.5 steals, built from the real 12 Nov 2026 snapshot; the receive and delivery times are illustrative. Other server messages: `welcome` (plan, delay, connections allowed, credits), `subscribed` (with the subscription id), `unsubscribed`, `credits` (each window charge), `limit` (the monthly limit body, then the socket closes with code 4029), `heartbeat` (every 25 s), `pong` and `error`.

```json
{
  "type": "event",
  "event": "price.changed",
  "market_id": "01a1d00f-ec01-71c0-9776-0facc97e44a7",
  "outcome_id": "01a1d00f-ec01-71c0-9776-0fb0230cbd18",
  "event_id": "01a1cac7-5c83-7e41-ba84-afe1af0c1ca6",
  "league": "NBA",
  "player": "cooper-flagg",
  "observed_at": "2026-11-12T17:23:56.000Z",
  "received_at": "2026-11-12T17:23:58.120Z",
  "delivered_at": "2026-11-12T17:23:58.131Z",
  "delay_seconds": 0,
  "data": {
    "bid_price": 0.39,
    "bid_qty": 18938,
    "last_price": 0.415,
    "status": "PENDING",
    "volume": 198265,
    "open_interest": 146324
  }
}
```

## Fields

Response fields

| Field | Type | Description |
| --- | --- | --- |
| type | string | `event` for data; `welcome`, `subscribed`, `unsubscribed`, `credits`, `limit`, `heartbeat`, `pong` or `error` otherwise. |
| event | string | `price.changed`, `trade`, `line.move` or `steam`. |
| market_id, outcome_id, event_id, league, player | string or null | What changed. Join to [GET /v1/markets](https://novigapi.com/docs/endpoints/markets) for full state. |
| observed_at | string (ISO 8601) | When our collector saw the change on Novig, in UTC. |
| received_at, delivered_at | string (ISO 8601) | When the gateway got the change and when it sent it to you, after any plan delay. |
| delay_seconds | integer | Added plan delay: 300 on Free, 0 on paid plans. |
| data | object or null | The change itself. `price.changed`: `bid_price`, `bid_qty`, `last_price`, `status`, `volume`, `open_interest` (the same fields as a [line history](https://novigapi.com/docs/endpoints/line-history) point). `trade`: `trade_id`, `price`, `qty`, `source`. `line.move` and `steam`: `id`, `from_price`, `to_price`, `delta_pp`, `kind` (a [line moves](https://novigapi.com/docs/endpoints/line-moves) row). |


## Credit cost

Connection time consumes credits: 2 credits at the start of each 285-second window a connection stays open, from the same monthly balance as REST (the first window is charged when you connect). The socket stays open across windows; each charge arrives as a `credits` message. When the month cannot cover the next window you get a `limit` message and the socket closes. One connection held open for 30 days is about 18,190 credits; messages themselves are free.

## Python

```python
# pip install websockets
import asyncio, json, os
import websockets

async def main():
    headers = {"Authorization": f"Bearer {os.environ['NOVIGAPI_API_KEY']}"}
    while True:  # reconnect after a network drop or a server restart
        async with websockets.connect("wss://stream.novigapi.com/v1/stream", additional_headers=headers) as ws:
            await ws.send(json.dumps({"op": "subscribe", "league": "NBA", "types": ["price.changed", "line.move"]}))
            async for raw in ws:
                msg = json.loads(raw)
                if msg["type"] == "event":
                    print(msg["event"], msg["market_id"], msg["data"], msg["delay_seconds"], "s delay")
                elif msg["type"] == "limit":
                    print(msg.get("user_message"))
                    return

asyncio.run(main())
```

## JavaScript

```javascript
// Node 22+ has a global WebSocket but it cannot send headers; use the ws package on servers.
import WebSocket from "ws";

function connect() {
  const ws = new WebSocket("wss://stream.novigapi.com/v1/stream", { headers: { Authorization: "Bearer " + process.env.NOVIGAPI_API_KEY } });
  ws.on("open", () => ws.send(JSON.stringify({ op: "subscribe", event_id: "01a1cac7-5c83-7e41-ba84-afe1af0c1ca6" })));
  ws.on("message", (raw) => {
    const msg = JSON.parse(raw.toString());
    if (msg.type === "event") console.log(msg.event, msg.market_id, msg.data);
  });
  ws.on("close", (code) => { if (code !== 4029) setTimeout(connect, 1000); });
}
connect();
```

## Notes and edge cases

- Free connections see every change 300 seconds late. That is fine for building and testing, not for trading decisions.
- Opening more connections than your plan allows is refused at the upgrade with HTTP 429 `STREAM_CONNECTION_LIMIT`, naming the connections allowed and open; existing connections are untouched.
- A connection holds up to 50 subscriptions; more returns an `error` with `TOO_MANY_SUBSCRIPTIONS`. Subscribe by league or event to cover more markets at once.
- After a reconnect, call [GET /v1/markets](https://novigapi.com/docs/endpoints/markets) once to resync; the stream does not replay what you missed.
- Tell your own users the true latency: changes arrive within 5 to 30 seconds of the move on Novig.

## Sources

- [Novig docs: Streaming connection](https://docs.novig.com/api/streaming/connection.md)
- [Novig API reference: Get the order book](https://docs.novig.com/api-reference/catalog/get-the-order-book.md)
- [Novig API docs](https://docs.novig.com)

---

Get a free API key: https://novigapi.com/sign-up . Docs: https://novigapi.com/docs . MCP: https://novigapi.com/api/mcp . Operated by Stunning Couches LLC. Independent. Not affiliated with, endorsed by or sponsored by Novig.
