Overview

Connect to the public WebSocket, read the envelope, handle reconnects and limits.

What you get

The Market Stream is a public WebSocket that pushes live token data. xAxios indexes and processes on-chain data, and sends you the result as plain JSON: what a token is worth, every trade and liquidity move behind it, and every token the chain mints. No key, no signup, no SDK. Any WebSocket client in any language works.

  • Endpoint: wss://api.xaxios.com/v1/stream
  • Every message, in both directions, is a single JSON object.
  • Chains: Solana and Robinhood Chain. On a token channel you can leave chain off and we read it from the address, so a base58 address is Solana and a 0x address is Robinhood Chain.
  • Times are UTC unix timestamps in seconds. Money and token amounts carry six decimals, so a fraction of a cent survives. Percentages are whole numbers, so 3.01 means 3.01%. Prices are rounded to eight significant digits rather than to decimal places, because a token can trade at 0.000000001234.
  • A field is null when we have nothing to report for it. It never means zero.

Channels

Five channels, each with its own page. A token channel takes addresses; a chain channel takes a chain and follows every token on it. A state channel replays the latest to you the moment you subscribe; an event channel sends what happens while you are connected.

Send { "method": "streams" } and the server lists them back with a one-line description each, so a client can discover a new channel without redeploying.

Connect and subscribe

Open the socket and send a subscribe. You get an acknowledgement first, then the events for that subscription.

javascript
const socket = new WebSocket('wss://api.xaxios.com/v1/stream');

socket.onopen = () => {
  socket.send(JSON.stringify({
    id: 1,
    method: 'subscribe',
    stream: 'token.market',
    keys: ['EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm'],
  }));
};

socket.onmessage = (event) => {
  const message = JSON.parse(event.data);

  if (message.type === 'update' || message.type === 'snapshot') {
    console.log(message.key, message.data.priceUsd, message.data.marketCapUsd);
  }
};

Stop a subscription with the same message and method: 'unsubscribe'. Closing the socket releases everything you had open.

Try it live

Connect from this page. Pick a channel, keep the sample token or paste your own, and watch the events arrive. Your browser talks to the stream directly, so what you see here is exactly what your own client would get.

Not connectedwss://api.xaxios.com/v1/stream

Pick a channel and connect. Nothing is sent anywhere but your browser.

Messages you send

Four methods. subscribe and unsubscribe take a channel and up to 20 addresses at once, or a chain on the chain-scoped channels. ping answers with a pong. streams lists every channel.

json
{ "id": 1, "method": "subscribe", "stream": "token.market", "keys": ["<address>", "<address>"] }
{ "id": 2, "method": "subscribe", "stream": "tokens.created", "chain": "solana" }
{ "id": 3, "method": "unsubscribe", "stream": "token.market", "keys": ["<address>"] }
{ "id": 4, "method": "ping" }
{ "id": 5, "method": "streams" }
FieldTypeDescription
methodstringWhat you want: "subscribe", "unsubscribe", "ping" or "streams".
streamstringChannel name, e.g. "token.market". Required for subscribe and unsubscribe.
keysstring[]Token addresses, 1 to 20 per message. Required on a token channel, unused on a chain one.
chainstring | undefined"solana" or "robinhood". Optional on a token channel, where the chain is read from each address; required on a chain channel.
idstring | number | undefinedOptional request id. Every reply to it comes back with your value in requestId.

Adding and dropping tokens

Subscriptions build up on the connection. Start with one token, add more whenever you need them, and drop the ones you stopped showing. Each message only touches the addresses it names, so the rest keep running untouched.

json
{ "id": 1, "method": "subscribe", "stream": "token.market", "keys": ["<first>"] }
{ "id": 2, "method": "subscribe", "stream": "token.market", "keys": ["<second>", "<third>"] }
{ "id": 3, "method": "unsubscribe", "stream": "token.market", "keys": ["<second>"] }

After those three messages you are watching the first and third tokens. Subscribing again to something you already have is answered with another subscribed rather than counted twice, so a client that retries after a lost acknowledgement stays correct. Unsubscribing from something you do not have is not an error either: you asked to not be subscribed, and you are not.

Each token counts once against the cap of 20 per connection, whichever channel it is on, and dropping one frees its slot straight away.

Events you receive

Every event is one JSON object with the same envelope. Each one carries an id of ours, the type tells you what it is, and data carries the channel payload. When the event answers a request you sent an id with, it comes back as requestId.

json
{ "id": "4d1f...", "requestId": 1, "stream": "token.market", "type": "subscribed", "chain": "solana", "key": "<address>", "unix": 1758182400 }
{ "id": "8a02...", "stream": "token.market", "type": "update", "chain": "solana", "key": "<address>", "seq": 2, "unix": 1758182401, "data": { } }
{ "id": "b731...", "requestId": 4, "stream": "system", "type": "pong", "unix": 1758182402 }
{ "id": "c0e5...", "stream": "system", "type": "error", "unix": 1758182403, "error": { "code": "rate_limited", "message": "At most 120 messages per minute" } }
FieldTypeDescription
idstringOur id for this event: 32 hex characters, unique, on every event we send. Nothing is encoded in it.
typestringThe event kind: "subscribed", "unsubscribed", "snapshot", "update", "pong", "streams" or "error".
streamstringThe channel the event belongs to, or "system" for replies that belong to no channel.
chainstring | undefinedChain the event belongs to. Absent on system events.
keystring | undefinedToken the event belongs to. Absent on system events and on chain-scoped channels. EVM addresses come back lowercased.
seqnumber | undefinedCounter per subscription, starting at 1, on snapshot and update. A gap means you missed an event.
unixnumberWhen we sent the event, as a UTC unix timestamp in seconds.
dataobject | undefinedThe payload. Its shape is set by the channel.
errorobject | undefinedOn "error" only: { code, message }.
requestIdstring | number | undefinedEcho of the id you sent on the request this event answers. Present only on replies.

subscribed always arrives before the data it acknowledges. On a state channel, snapshot is what we already hold, sent the moment you subscribe, and update is every change after that. Event channels send only update, one per thing that happened.

Data arrives on change

A token that is not trading produces no events. Quiet tokens can go minutes without one, and an address we do not track sends nothing at all. Silence means no change, not a broken connection: use ping to tell the two apart.

Staying connected

We send a WebSocket ping every 30 seconds. Your client library answers it on its own, and a connection that misses a round is closed. In a browser you cannot see those pings, so send your own ping every 20 to 30 seconds: if no pong comes back within a few seconds, the connection is gone even when no close event fired.

Subscriptions live on the connection. After a reconnect, send them again, and back off between attempts so a restart on our side does not turn into a stampede. What happens further upstream is our problem, not yours: if a feed we depend on drops, we reconnect and resubscribe underneath you, your seq keeps counting, and you are asked to do nothing.

javascript
const TOKENS = ['EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm'];

let socket;
let retries = 0;
let heartbeat;
let pongTimer;

function connect() {
  socket = new WebSocket('wss://api.xaxios.com/v1/stream');

  socket.onopen = () => {
    retries = 0;
    socket.send(JSON.stringify({ method: 'subscribe', stream: 'token.market', keys: TOKENS }));

    // Ask for a pong regularly; if one does not come back, treat the socket as dead.
    heartbeat = setInterval(() => {
      socket.send(JSON.stringify({ method: 'ping' }));
      pongTimer = setTimeout(() => socket.close(), 5000);
    }, 20000);
  };

  socket.onmessage = (event) => {
    const message = JSON.parse(event.data);
    if (message.type === 'pong') clearTimeout(pongTimer);
    if (message.type === 'update' || message.type === 'snapshot') render(message.key, message.data);
  };

  socket.onclose = () => {
    clearInterval(heartbeat);
    clearTimeout(pongTimer);
    // 1s, 2s, 4s ... capped at 30s.
    setTimeout(connect, Math.min(30000, 1000 * 2 ** retries++));
  };
}

connect();

Watch the seq

seq counts events per subscription, starting at 1. If it jumps, you missed something. On a state channel the next update carries the full state anyway, so a gap costs you history, never correctness; on an event channel a gap is events you will not see again.

Errors

An error is an event like any other, with type: 'error' and an error.code you can branch on. Send an id with your request and it comes back on the error as requestId, so you know which message failed. Only too_many_connections closes the socket.

CodeWhat it means
invalid_messageThe message was not JSON, or a field is missing or malformed. The text says which.
unknown_streamNo channel by that name.
too_many_connectionsYou already hold the maximum number of open connections. Sent just before the socket closes with code 1008.
too_many_subscriptionsThis connection is at its subscription cap. Unsubscribe from something, or open another connection.
rate_limitedYou sent more messages than the per-minute budget. Slow down; the connection stays open.
subscribe_failedWe could not open the subscription. Retry it.
internal_errorSomething broke on our side. Retry the message.

Limits

The stream is free and open, so the limits exist to keep one client from crowding out the rest.

20tokens per connection
5connections per client, so 100 tokens in all
120messages per minute, per connection
16 KBmaximum size of a message you send
20addresses per subscribe or unsubscribe message

Twenty tokens on one connection, five connections per client: a hundred tokens in all, which is what a dashboard needs. Spread them across the five rather than opening a connection per token, and unsubscribe from what you stopped showing. A client that stops reading what we send is disconnected once the backlog grows past what we are willing to hold for it.

프리미엄 2주간 무료

기간 한정으로 누구나: Rug Check를 포함한 모든 프리미엄 기능을 무료로 이용하세요.