API
WebSocket
Live-Daten von Seeryx über einen WebSocket: Abonniere eine Runde für beide Orderbücher, Trades und Volumen sowie dein Konto; Nachrichtenformate, Neuverbindung.
Verbinden
- URL
wss://seeryx.com/ws- Abonnements
- Eine Runde und ein Konto pro Verbindung. Ein neues Abonnement derselben Art ersetzt das alte; für mehr Runden brauchst du mehr Verbindungen.
- Zustellung
- Die erste Nachricht nach einem Abonnement ist der vollständige Zustand. Danach kommt eine Nachricht nur, wenn sich ihr Inhalt geändert hat, und zwar sofort.
Abonnieren
Du sendest
{ "subscribe": "round", "slug": "btc-updown-5m-1791916200" }
{ "subscribe": "account", "token": "eyJ0eXAiOiJKV1Qi..." }Nachrichten
Du erhältst
{ "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: beide Orderbücher vollständig (wieGET /api/orderbook), die letzten 100 Trades und das Volumen.account: genauGET /api/account. Wenn sichheadsändert, lies/api/activityoder/api/settledfür die neuen Zeilen.errorlässt den Socket offen; ein weiteres Abonnement kann folgen.
Ping und Pong
Der Server sendet alle 30 Sekunden einen WebSocket-Ping; WebSocket-Bibliotheken antworten von selbst mit einem Pong. Eine Verbindung, von der 120 Sekunden lang nichts ankommt, wird geschlossen. Sende das erste Abonnement innerhalb von 10 Sekunden nach dem Verbinden.
Neuverbindung
- Verbinde dich mit wachsender Verzögerung neu (1, 2, 4 … bis zu 30 Sekunden) und abonniere erneut: Die erste Nachricht bringt den vollständigen Zustand, sodass nichts verloren geht.
- Ein Close mit dem Grund „Token expired“ bedeutet: Melde dich erneut an, bevor du das Konto abonnierst.
- Wenn eine Runde endet, abonniere die nächste über dieselbe Verbindung.
Limits und Close-Codes
| Limit | Wert | Bei Überschreitung |
|---|---|---|
| Nachrichtengröße | 4 KB | Die Verbindung wird geschlossen |
| Nachrichten einer Verbindung | 60 pro Minute | Close 1008 „Rate limit“ |
| Abonnement-Nachrichten | 600 pro Minute und IP-Adresse, 60 pro Konto | { "type": "error", "error": "Too many subscriptions" } |
| Offene Verbindungen | 50 pro IP-Adresse | HTTP 429 vor dem Upgrade, mit Retry-After |
| Erstes Abonnement | Innerhalb von 10 Sekunden | Close 1008 „Handshake timeout“ |
| Stille | 120 Sekunden ohne jeden Frame | Close 1008 „Idle timeout“ |
| Ungelesene Nachrichten | 256 in der Warteschlange | Close 1013 „Slow consumer“ |
| Anmelde-Token | Bis es abläuft oder widerrufen wird | Close 1008 „Token expired“ oder „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(); },
};
}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);
},
});