Skip to main content
The Totalis API provides a WebSocket endpoint for real-time updates on RFQs and quotes. This is essential for market makers who need to react quickly to new RFQs and for users tracking their active bets.

Connecting

ws://localhost:3000/ws        # Development
wss://api.totalis.com/ws      # Production

Authentication

After connecting, send an auth message with your API key:
{
  "type": "auth",
  "api_key": "api_live_xxxxxxxxxxxxxxxxxxxxx"
}
You’ll receive either:
{ "type": "auth:success", "user_id": "did:privy:abc123", "user_type": "user" }
or:
{ "type": "auth:error", "message": "Invalid API key" }
WebSocket only supports API key authentication. Privy JWT tokens are not supported.

Subscribing to Channels

After authenticating, subscribe to channels to receive events:
{ "type": "subscribe", "channel": "rfqs:open" }

Available Channels

ChannelAudienceDescription
rfqs:openMarket makersAll new and open RFQs
rfq:{rfq_id}RFQ ownerUpdates for a specific RFQ
mm:quotes:{mm_id}Market makerUpdates on your own quotes

Events

RFQ Events

EventDescriptionData
rfq:createdNew RFQ createdFull RFQ object
rfq:cancelledRFQ cancelled{ rfq_id, reason }
rfq:statusRFQ status changed{ rfq_id, status, updated_at, accepted_quote_id }
rfq:expiredRFQ expired{ rfq_id }

Quote Events

EventDescriptionData
quote:submittedNew quote submittedFull quote object
quote:acceptedQuote accepted by user{ quote_id, rfq_id, confirmation_deadline }
quote:confirmedQuote confirmed by MM{ quote_id, rfq_id, execution_id }
quote:executedOn-chain escrow executed{ quote_id, rfq_id }
quote:expiredQuote expired{ quote_id, rfq_id }
quote:rejectedQuote rejected (another was accepted){ quote_id, rfq_id, reason }

Keep-Alive

Send periodic pings to keep the connection alive:
{ "type": "ping" }
Response:
{ "type": "pong", "timestamp": "2025-02-07T12:00:00.000Z" }

Example: Market Maker Bot

const ws = new WebSocket('wss://api.totalis.com/ws');

ws.onopen = () => {
  // Authenticate
  ws.send(JSON.stringify({
    type: 'auth',
    api_key: 'api_live_xxxxxxxxxxxxxxxxxxxxx'
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case 'auth:success':
      // Subscribe to open RFQs
      ws.send(JSON.stringify({ type: 'subscribe', channel: 'rfqs:open' }));
      ws.send(JSON.stringify({ type: 'subscribe', channel: `mm:quotes:${msg.user_id}` }));
      break;

    case 'rfq:created':
      console.log('New RFQ:', msg.data);
      // Analyze and potentially submit a quote
      break;

    case 'quote:accepted':
      console.log('Quote accepted, confirming...');
      // Confirm the quote within 60 seconds
      break;
  }
};

// Keep alive
setInterval(() => {
  ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);