Docs

API

Streaming

Live prices, GEX and chains over one WebSocket — tickets, the auth frame, subscriptions, control frames, and reconnecting correctly.

The streaming API is one WebSocket. You exchange your key for a short-lived ticket, open the socket, authenticate with the ticket in the first frame, then subscribe to topics. Control messages are JSON text frames; data arrives as binary protobuf Frame messages, defined in ws-protocol.proto.

Streaming is not included on the free tier: minting a ticket returns 403 not_entitled_tier.

1. Mint a ticket

sh
curl -sS -X POST "https://api.itmatrixhq.com/v2/stream/ticket" \
  -H "Authorization: Bearer $ITM_API_KEY"
json
{ "data": { "ticket": "eyJ…", "exp": 1790257013559 }, "meta": {} }

exp is epoch milliseconds. A ticket authorizes one connection handshake and carries your entitlements, so mint a fresh one for every new connection. It does not need renewing while the connection is healthy.

2. Connect and authenticate

Open wss://api.itmatrixhq.com/v2/ws. The HTTP upgrade takes no Authorization header; authentication happens in-band. Send this as the first frame:

json
{ "op": "auth", "ticket": "eyJ…", "proto": 3, "encoding": "protobuf" }

The server answers {"op": "authed"}. Anything else first, or a bad ticket, closes the connection with invalid_ticket.

3. Subscribe

json
{ "op": "sub", "id": "gex-spy", "topic": "gex", "symbol": "SPY" }

id is yours; the server echoes it. It replies {"op": "ack", "id": "gex-spy", "effective": {…}} with the subscription as it will actually be served, or {"op": "nack", "id": "gex-spy", "code": "…", "message": "…"} with a code from the error set. Entitlement is re-checked on every subscription.

TopicData framesOptions
stockstrade, mid, bar_open, bar_close, quotewant: any of trades, mid, bars, quote; timeframe
spotspot—
gexgex_snap, then gex_deltaexpiries, by_expiry
chainchain_snap, then chain_deltaexpiry, strike_gte, strike_lte (integer thousandths)

To stop one: {"op": "unsub", "id": "gex-spy"}.

Each connection caps how many subscriptions and distinct symbols it may hold; a new one past the cap is refused with nack code quota_exceeded.

4. Data frames

Every binary message is exactly one protobuf Frame: the symbol plus one payload (trade, spot, gex_snap, …). Prices in frames are scaled integers — divide *_scaled fields by 10,000 for dollars. Timestamps are epoch milliseconds; strikes are integer thousandths.

5. Control frames

Server sendsYou do
{"op": "ping", "t": 42}Reply {"op": "pong", "t": 42} with the same t.
{"op": "bye", "reconnect_after_ms": 2000}The server is closing this connection. Reconnect after the given delay.
{"op": "nack", …}That subscription is gone; decide whether to retry it.

6. Reconnecting

When the socket closes unexpectedly:

  1. Wait, then reconnect. Start at about 250 ms and double each failed attempt up to about 10 s; reset the delay after a successful authed. If the server sent bye with reconnect_after_ms, wait that long instead.
  2. Mint a new ticket for the new connection.
  3. After authed, re-send every active subscription.

Do not tear down a healthy connection because its handshake ticket's exp has passed — the ticket only matters at the handshake.

If your consumer falls behind, do not silently drop frames. The SDKs keep a bounded queue per subscription (64 frames by default) and raise an error when it overflows, so data loss is always explicit.

A complete client

Everything above in one program: mint a ticket, authenticate, subscribe, answer pings. Reconnecting is left out to keep it short; add it with the rules in Reconnecting. The SDK version of each tab uses the Python or TypeScript SDK, which owns all of this for you: one shared connection, tickets, pings, reconnects and resubscription. Python streaming through the SDK needs the extra: pip install "itmatrix[stream]".

import asyncio
import json
import os

import httpx
import websockets  # pip install websockets


async def main():
    ticket = httpx.post(
        "https://api.itmatrixhq.com/v2/stream/ticket",
        headers={"Authorization": f"Bearer {os.environ['ITM_API_KEY']}"},
    ).json()["data"]["ticket"]

    async with websockets.connect("wss://api.itmatrixhq.com/v2/ws") as ws:
        await ws.send(json.dumps({"op": "auth", "ticket": ticket, "proto": 3, "encoding": "protobuf"}))
        await ws.send(json.dumps({"op": "sub", "id": "gex-spy", "topic": "gex", "symbol": "SPY"}))
        async for message in ws:
            if isinstance(message, bytes):
                print("frame:", len(message), "bytes")  # one protobuf Frame
                continue
            control = json.loads(message)  # authed, ack, nack, ping, bye
            if control["op"] == "ping":
                await ws.send(json.dumps({"op": "pong", "t": control["t"]}))
            else:
                print(control)


asyncio.run(main())

Frames arrive as binary protobuf Frame messages; decode them with bindings generated from ws-protocol.proto.

In the SDKs, subscriptions are async iterators. Closing one (leaving the async with, or break out of the loop) unsubscribes it, and the socket closes when the last subscription does.