This page hasn't been translated yet. Showing English version.

WebSocket

One socket for every market and topic: sequence numbered book deltas, best bid and offer, oracle marks, trades, klines and the list feed, with exact frames.

The WebSocket is the transport for market makers and latency sensitive bots. One connection carries every market and topic you ask for; the order book arrives as sequence numbered deltas and bars are pushed as they form. The data is identical to the SSE feed; what changes is the delivery.

GET wss://app.truefinance.ai/api/ws/perps
Rollout

During the rollout the socket is allowlisted per account. Send the app session as Authorization: Bearer <token> on the upgrade (or ?token= from a browser). A refused account gets HTTP 403 on the upgrade and should fall back to SSE, which carries the same data. Planned: API key auth on the upgrade for bots and market makers, see Limits and Security.

Lifecycle

01
Connect
Upgrade with the bearer. Other answers: 403 not in the gate, 429 too many sockets from one IP, 503 server full.
02
Ready
First frame {"op":"ready","ts":…,"maxTopics":40,"source":"poll"|"gateway"}. source says where your feed comes from; content is identical.
03
Subscribe
Send {"op":"subscribe","args":[…]}. Ack lists success and failed topics. The current state of each topic arrives at once.
04
Receive
Every data frame is {topic, type, ts, data}.
05
Keepalive
Server sends {"op":"ping"} every 20 s; reply {"op":"pong"}. Two missed pongs close the socket. Close codes: 1000 "reauth" when the token expires or after 24 h, 1012 server restarting, 1006 cut for missed pongs or a slow reader. When a market upstream stops updating for 10 s its subscribers get {"op":"stale","market":…,"since":…}, then {"op":"fresh","market":…} on recovery; while stale, read REST.

Subscribing

{"op":"subscribe","args":["orderbook:BTCUSDC","bbo:BTCUSDC","mark:BTCUSDC","trades:BTCUSDC","ticker:BTCUSDC","kline:1m:BTCUSDC","marks:ALL","stats:ALL","tape:ALL"]}

Acknowledged with {"op":"subscribe","success":[…],"failed":[{"topic":"…","reason":"bad_topic"|"unknown_market"|"topic_limit"|"rate_limited"|"feed_limit"}]}. rate_limited: this connection started too many new market feeds at once (40 at once, then 1 per second); retry the topic after a second. feed_limit: the server is at its feed capacity; retry later. Drop topics with {"op":"unsubscribe","args":[…]}. At most 40 topics per connection.

Topics

TopicFrames
orderbook:SYMtype:"snapshot" then type:"delta", sequence numbered (below)
bbo:SYMtype:"update", {bid, bid_size, ask, ask_size, …}
ticker:SYMtype:"update", the 24h stats row, only when a non timestamp field changed
mark:SYMtype:"update", {market, mark_price}, the oracle mark
trades:SYMtype:"delta", array of new fills; no replay on subscribe
kline:<interval>:SYMtype:"update" for the forming bar, type:"closed" when it rolls; intervals 1s 1m 5m 15m 1h 4h 1d; the open bar is sent on subscribe
marks:ALLtype:"update", {marks:{SYM:px}, ts, src:"pyth"}, only when a mark moved
stats:ALLtype:"snapshot", {results:[…]} for every market, on change
tape:ALLtype:"update", {trades_24h, trades_today, ts}

Order book deltas

Snapshot:

{"topic":"orderbook:BTCUSDC","type":"snapshot","ts":1789232208665,"data":{"type":"snapshot","market":"BTCUSDC","u":123,"bids":[["77302","0.3593"],["77307","0.4"]],"asks":[["77350","0.4061"]]}}

Delta:

{"topic":"orderbook:BTCUSDC","type":"delta","ts":1789232209271,"data":{"type":"delta","market":"BTCUSDC","u":124,"prevU":123,"bids":[["77302","0"]],"asks":[["77350","0.5"]]}}

Each level is [price, size]; size "0" removes the level. Apply a delta only when prevU equals the u you hold. Otherwise you missed a frame: resubscribe (subscribing a book topic you already hold returns a fresh snapshot) orderbook:SYM to receive a fresh snapshot. Never render a book after a gap. Measured over 1,000 polls, a delta averages 105 bytes where the full book is 2,462, and 4 in 10 polls emit nothing at all.

A minimal client

const ws = new WebSocket("wss://app.truefinance.ai/api/ws/perps", [], { headers: { Authorization: `Bearer ${TOKEN}` } });
let book = null;
ws.onopen = () => ws.send(JSON.stringify({ op: "subscribe", args: ["orderbook:BTCUSDC", "mark:BTCUSDC"] }));
ws.onmessage = (ev) => {
  const f = JSON.parse(ev.data);
  if (f.op === "ping") return ws.send(JSON.stringify({ op: "pong" }));
  if (f.op) return; // ready, subscribe ack
  if (f.topic === "orderbook:BTCUSDC") {
    const d = f.data;
    if (d.type === "snapshot") book = { u: d.u, bids: new Map(d.bids), asks: new Map(d.asks) };
    else if (book && d.prevU === book.u) { apply(book, d); book.u = d.u; }
    else resubscribe("orderbook:BTCUSDC"); // gap
  }
  if (f.topic === "mark:BTCUSDC") markToMarket(f.data.mark_price);
};

Limits

40 topics per socket, 30 client frames in a burst and 5 a second sustained (ping and pong not counted), 6 sockets and 60 handshakes a minute per account, 32 sockets and 240 handshakes a minute per client IP. Reconnect with backoff, 1 s doubling to 15 s; a socket that connected and later dropped is reconnected by you, the server does not replay.

Last updated: