accueil

API

WebSocket

Les données Seeryx en direct sur un seul WebSocket : une manche (ses deux carnets, transactions et volume) et votre compte ; formats et reconnexions.

Connexion

URL
wss://seeryx.com/ws
Abonnements
Une manche et un compte par connexion. Un nouvel abonnement du même type remplace l’ancien ; pour suivre davantage de manches, ouvrez davantage de connexions.
Distribution
Le premier message après un abonnement contient l’état complet. Ensuite, un message n’arrive que lorsque son contenu a changé, dès qu’il a changé.

Abonnement

Vous envoyez
{ "subscribe": "round", "slug": "btc-updown-5m-1791916200" }
{ "subscribe": "account", "token": "eyJ0eXAiOiJKV1Qi..." }
Un slug de manche ou de marché événementiel ; le compte nécessite votre jeton de connexion.

Messages

Vous recevez
{ "type": "round", "slug": "btc-updown-5m-1791916200",
  "up":   { "bids": [[13, 10], [12, 100]], "asks": [[16, 100], [17, 100]] },
  "down": { "bids": [[84, 100], [83, 100]], "asks": [[87, 10], [88, 100]] },
  "trades": [{ "id": 13, "side": "down", "action": "buy", "shares": 10, "price": 53, "time": 1791916330063 }, ...],
  "volume": { "volume": 55.845724, "trades": 13 } }

{ "type": "account",
  "positions": [{ "slug": "btc-updown-5m-1791916200", "side": "down", "shares": 50.3462, "cost": 18.126554 }],
  "orders": [{ "id": 25538, "slug": "btc-updown-5m-1791916200", "side": "down", "action": "buy", "shares": 10, "limit": 48, "createdAt": 1791916330045 }],
  "balance": { "available": 474.097965, "locked": 0, "total": 474.097965, "currency": "USDC" },
  "heads": { "activity": 116, "settled": null } }

{ "type": "error", "error": "Invalid subscription or authentication" }
  • round : les deux carnets complets (comme GET /api/orderbook), les 100 dernières transactions et le volume.
  • account : exactement GET /api/account. Lorsque heads change, lisez /api/activity ou /api/settled pour obtenir les nouvelles lignes.
  • error laisse la connexion ouverte ; un autre abonnement peut suivre.

Maintien de la connexion

Le serveur envoie un ping WebSocket toutes les 30 secondes ; les bibliothèques WebSocket répondent d’elles-mêmes par un pong. Une connexion dont rien n’arrive pendant 120 secondes est fermée. Envoyez le premier abonnement dans les 10 secondes qui suivent la connexion.

Reconnexion

  • Reconnectez-vous avec un délai croissant (1, 2, 4… jusqu’à 30 secondes) et abonnez-vous de nouveau : le premier message apporte l’état complet, rien n’est donc manqué.
  • Une fermeture avec le motif "Token expired" signifie que vous devez vous reconnecter avant de vous abonner au compte.
  • Lorsqu’une manche se termine, abonnez-vous à la suivante sur la même connexion.

Limites et codes de fermeture

LimiteValeurAu-delà
Taille des messages4 KoLa connexion se ferme
Messages d’une connexion60 par minuteFermeture 1008 "Rate limit"
Messages d’abonnement600 par minute et par adresse IP, 60 par compte{ "type": "error", "error": "Too many subscriptions" }
Connexions ouvertes50 par adresse IPHTTP 429 avant le passage au protocole WebSocket, avec Retry-After
Premier abonnementDans les 10 secondesFermeture 1008 "Handshake timeout"
Inactivité120 secondes sans aucune trameFermeture 1008 "Idle timeout"
Messages non lus256 en file d’attenteFermeture 1013 "Slow consumer"
Jeton de connexionJusqu’à son expiration ou sa révocationFermeture 1008 "Token expired" ou "Token revoked"

Code

live.ts
// live.ts: a WebSocket client that reconnects by itself. Node 22+ (WebSocket is built in) or any browser.
import { ACCESS, BASE } from './seeryx';

export type Message = { type: 'round' | 'account' | 'error' } & Record<string, unknown>;

export function live(options: { slug?: string; token?: string; onMessage: (message: Message) => void }) {
  let socket: WebSocket | null = null;
  let attempt = 0;
  let stopped = false;
  let { slug, token } = options;
  const gate = ACCESS ? { access: ACCESS } : {};
  const send = (message: object) => {
    if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ ...message, ...gate }));
  };

  function connect() {
    socket = new WebSocket(`${BASE.replace(/^http/, 'ws')}/ws`);
    socket.onopen = () => {
      attempt = 0;
      if (slug) send({ subscribe: 'round', slug });
      if (token) send({ subscribe: 'account', token });
    };
    socket.onmessage = (event) => options.onMessage(JSON.parse(String(event.data)));
    socket.onclose = (event) => {
      if (stopped) return;
      if (event.reason === 'Access code required') { stopped = true; return; } // get a new access token
      if (event.reason === 'Token expired') token = undefined;                   // sign in again for it
      setTimeout(connect, Math.min(30_000, 1000 * 2 ** attempt++));              // 1 s, 2 s, 4 s ... 30 s
    };
  }
  connect();

  return {
    // one round per connection: subscribing to the next round replaces the previous one
    follow(next: string) { slug = next; send({ subscribe: 'round', slug }); },
    close() { stopped = true; socket?.close(); },
  };
}
Un client qui se reconnecte de lui-même.
watch.ts
import { api, loadWallet, signIn } from './seeryx';
import { live } from './live';

const token = await signIn(await loadWallet());
const { slug } = await api<{ slug: string }>('/markets/BTC/5m/current');
live({
  slug, token, onMessage: (m) => {
    if (m.type === 'round') console.log('Up book', m.up);
    if (m.type === 'account') console.log('cash', m.balance);
  },
});