DocsAPI

Live streams

Get live trades, PnL, wallet balances and task changes over one WebSocket with your API key.

A WebSocket is a connection that stays open, so the server can push news to you the moment it happens. Use it instead of asking a REST route again and again.

Before you start#

  • Connect to wss://api.vortexdeployer.com/ws. API keys work only at this address.
  • Your key needs the right scope for each topic. See Topics.
  • Run this from a server. Never open a keyed socket in a web page: anyone could read the key there.

Connect and subscribe#

Every message is one JSON object. Put your key in token on each subscribe.

const ws = new WebSocket('wss://api.vortexdeployer.com/ws');
ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'subscribe', topic: 'txs:7xKX…pump', token: 'vd_…' }));
  setInterval(() => ws.send(JSON.stringify({ type: 'ping' })), 25_000);
};
ws.onmessage = (m) => console.log(JSON.parse(m.data));

What you send:

  • { "type": "subscribe", "topic": "…", "token": "vd_…" } — start a topic. The answer is { "type": "subscribed", "topic": "…" }, then the topic's first frame.
  • { "type": "unsubscribe", "topic": "…" } — stop a topic. The answer is { "type": "unsubscribed", "topic": "…" }.
  • { "type": "ping" } — the server answers { "type": "pong" }. Use it to check the line is alive.

When you connect, the server sends { "type": "welcome", "topics": [ … ] }. That list also holds topics the app uses. Only the topics below take an API key.

A refused subscribe answers { "type": "error", "topic": "…", "message": "…" }. The message says why, such as a missing scope.

Topics#

TopicScopeWhat you get
txsstreamEvery trade the platform sees, on every token.
txs:<mint>streamTrades on one token.
pnlstreamYour PnL on every token, on Solana.
pnl:<mint>streamYour PnL on one token.
wallet:<address>streamThe live balance of one of your wallets.
tasksreadYour running tasks, then every task change.
userreadA nudge when something in your account changed.

<mint> is the token's mint address. Any other topic with a key, like xid, is refused.

Trades#

txs sends every trade as it lands. It starts with { "type": "txs_snapshot", "topic": "txs", "note": "firehose: deltas only" }, then one trade frame per trade.

txs:<mint> starts with { "type": "trade_snapshot", "topic": "txs:<mint>", "mint": "…", "trades": [ … ] }. That list holds the token's recent trades. Then new trades follow.

{
  "type": "trade",
  "chain": "solana",
  "mint": "NnLz6wXfB3c41KwPWPvk9Rz75zR5enTGtYmsELrpump",
  "quote_mint": "So11111111111111111111111111111111111111112",
  "pool_address": "B8GeyGLChfj3Mn9HCyVJYS4nMRSMVDCCutRTwGasoH7V",
  "dex": "pump_swap",
  "side": "buy",
  "trader": "A2uqWi4Zm3yVYqvuRb4GF6BSb56pocvw1J2V2nQeU3QE",
  "base_amount": "44917",
  "quote_amount": "179054",
  "signature": "42RU7t2Drzyr…faT22",
  "block": 447482805,
  "block_index": 805,
  "timestamp": 1789549114,
  "pre_base_reserves": "2105102154648",
  "pre_quote_reserves": "8391614579937",
  "post_base_reserves": "2105102109731",
  "post_quote_reserves": "8391614758991"
}
Stream frames use snake_case

Frames keep the chain feed's names, like pool_address and block_index. Only REST answers use camelCase.

FieldMeaning
mintThe token that was traded.
quote_mintWhat it was traded against, like wrapped SOL.
pool_address, dexThe pool and the venue, like pump_swap.
sidebuy or sell.
traderThe wallet that traded.
base_amount, quote_amountThe two amounts, as strings in base units.
signature, block, block_index, timestampWhere it landed on the chain. timestamp is in Unix seconds.
pre_…_reserves, post_…_reservesWhat the pool held just before and just after. null when the venue does not say.

The reserves let you work out the pool's price without another call.

PnL#

A pnl:<mint> frame:

{ "type": "pnl_update", "topic": "pnl:7xKX…pump", "mint": "7xKX…pump", "at": 1757923203112, "pnl": { } }
  • pnl has the same fields as GET /pnl?mint=. It is null when the token cannot be priced, and reason says why.
  • On pnl with no mint, the frame has chain and tokens instead. tokens matches GET /pnl.
  • at is a time in milliseconds. It only goes up. Drop a frame whose at is not newer than the last one.
  • A frame comes at most once every 3 seconds per topic.
  • A frame with error means the PnL cannot be worked out right now.

Wallet balances#

wallet:<address> must name one of your own wallets. A key with a wallet list can open only wallets on that list. Any other address is refused.

You get a wallet_balance_snapshot first, then a wallet_balance_update each time the balance changes:

{
  "type": "wallet_balance_update",
  "chain": "solana",
  "address": "7tXi4mbvppPBgYgTVj2NpYq9K1KYYQvHv6tfCpJv9kMT",
  "sol": "420100000",
  "tokens": [ { "mint": "7xKX…pump", "amount": "3500000000" } ],
  "block": 447482806,
  "block_index": 806
}
  • sol — the coin balance as a string in base units: lamports on Solana, wei on an EVM chain.
  • tokens — the token balances it tracks, each mint and amount in base units.
  • block, block_index — where the balance was last seen. block_index can be null.

If no balance is saved yet, you get { "type": "wallet_balance_pending", "topic": "…" } first. The server reads the balance and sends an update.

To track one token from the start, add mint to the subscribe: { "type": "subscribe", "topic": "wallet:<address>", "mint": "7xKX…pump", "token": "vd_…" }.

Tasks#

tasks starts with your running and paused tasks:

{ "type": "task_snapshot", "topic": "tasks", "tasks": [ { "task_id": "6186", "task_type": "buy", "mint": "7xKX…pump", "status": "running", "label": "morning buys", "updated_at": "2026-09-16T01:09:24.531Z" } ] }

Then it sends three kinds of frames:

  • task_update — a task's status changed. It has task_id, task_type, mint, status, last_error and updated_at.
  • task_execution — a task sent a trade. It has task_id and an execution with id, side, wallet_id, amount_in, amount_out, signature, confirmed, err, source and created_at.
  • wallet_skipped — a task left a wallet out. It has task_id, wallet_id, mint, reason, source and timestamp.

See Tasks API for what each status means.

Account nudges#

user starts with { "type": "user_snapshot", "topic": "user", "note": "deltas only; re-read your REST resources now" }. Read what you need over REST at that point.

After that you get:

  • { "type": "invalidate", "resource": "wallets", "id": null } — that part of your account changed. Read it again over REST. resource names it, like wallets or portfolio.
  • { "type": "launch", … } — a launch moved on. stage is prepared, confirmed, unsettled or failed. The frame also has launch_id, project_id, mint, signature and err.

Rules and limits#

  • One key per socket. After the first keyed subscribe, a different key on the same socket is refused.
  • 5 sockets per key. A sixth socket gets an error on its first subscribe.
  • 50 txs/pnl topics and 50 wallet topics per socket.
  • 4 subscribes waiting at once. Wait for answers before you send more.
  • The key's IP list applies to sockets too.
  • A message you send can be up to 4,096 bytes.
  • Keys cannot use the socket for reads. Use REST.

These close the socket:

CodeWhy
4001The key was revoked, or its settings changed. Reconnect with a valid key.
400810 subscribes failed with a bad key. The next subscribe closes the socket.
1013Too many sockets from your IP address.

Slow readers and reconnects#

If you read too slowly, the server drops frames for you. When you catch up, it sends { "type": "resync", "topic": "…" } and then a fresh first frame for that topic. Treat it like a new start and read missed state over REST.

A drop loses your topics. Reconnect with a short, growing wait, then subscribe again.

See also#