Skip to main content

Stream Quotes

WS /api/v1/stream is a WebSocket endpoint that pushes live swap quotes. Subscribe to one or more token pairs and the server sends an updated quote whenever the on-chain price for that pair materially changes — no polling required.
The transport is raw JSON over WebSocket (plain text frames — not socket.io or STOMP). Open a standard WebSocket connection, then exchange the JSON messages described below.
Use streaming when you need a continuously up-to-date price (e.g. a swap UI showing a live rate). For one-off quotes, GET /api/v1/quote is simpler.

Connecting

This endpoint requires an API key like every other endpoint (see Authentication) — but since browsers can’t set custom headers on a WebSocket handshake, the key must be passed as a ?key= query parameter, not an Authorization header. A handshake without a valid key is rejected before the upgrade completes. The upgrade is guarded by the pre-auth per-IP limiter only — the per-plan key limits that apply to REST endpoints are not applied to the handshake (see Rate Limits). Once upgraded, the connection is long-lived and is not subject to any per-request rate limiter.

Client messages

Send a JSON text frame with an op and a pairs array.
string
required
Operation to perform: subscribe or unsubscribe for quote streaming, or subscribe_route / unsubscribe_route for CPI route templates (see Route templates below).
object[]
required
One or more pair specifications to subscribe or unsubscribe.
A subscription is keyed by the full (in, out, amount, exactIn) tuple — two subscriptions that differ only in amount are distinct streams.

Subscribe

On subscribe, the server sends an immediate first quote for each newly accepted pair (so you don’t wait for the next price change), followed by a subscribed ack. The first quote may arrive just before the ack.

Unsubscribe

Route templates

subscribe_route streams a ready-to-use CPI route template instead of a price. It is aimed at on-chain integrators (liquidation keepers, vault rebalancers) that want to fire without an HTTP round-trip: hold the latest template in memory and submit when your trigger fires. Send a routes array whose entries use the same request shape as POST /api/v1/cpi/route-accounts:
swapMode defaults to ExactIn. slippageBps, allowedIntermediateMints, maxHops, excludeDexes and referrer are optional and behave exactly as on the REST endpoint.
Route subscriptions are capped separately from quote subscriptions: 32 per connection (quotes allow 256). Each template is materially more expensive to maintain than a quote.

Server messages

Quote push

Sent for the immediate first quote and again only when the quote materially changes (a change in amountOut, priceImpactBps, or hops). Identical re-quotes are suppressed.
string
Always "quote".
string
Input token mint.
string
Output token mint.
string
The subscribed amount, in smallest units.
string
Estimated output amount in smallest units (ExactIn) — the live quote.
integer
Price impact in basis points.
integer
Number of hops in the route. 1 = direct, 2+ = multi-hop.
integer
The Fogo slot the quote was computed at.
string
Firm-quote commitment ID for this exact quote. Pass it as quoteId to POST /swap or POST /instructions to replay the quoted route at the quoted price. Omitted when the quote can’t be pinned. See Firm quotes.
integer
How long quoteId stays redeemable, in milliseconds.
integer
How long quoteId stays redeemable with firm: true (price-or-fail), in milliseconds.
string
Base58 Ed25519 signature over the canonical vulcx-quote-v1 message, present when the server has a signing key. Verify offline against the key at /.well-known/vulcx-quote-signer.
integer
Absolute expiry (unix milliseconds) embedded in the signed message.
ExactOut subscriptions can’t tell you the input. The quote push has no input-amount field at all — amount echoes what you subscribed with, and there is nothing carrying the computed input side. Since the input is the whole point of an exact-output quote, use GET /quote with swapMode=ExactOut for that, and treat exactIn: false subscriptions as change notifications rather than a price feed.

Route push

Sent for the first template on subscribe_route, then only when the template materially changes — the route plan, account list, or LUTs. Amount movements alone do not trigger a push.
string
Always "route".
string
The authority the template was built for.
string
Input token mint.
string
Output token mint.
string
The subscribed amount, in smallest units.
string
ExactIn or ExactOut.
integer
Increases by 1 per template change for this subscription. A gap means you missed a push — resubscribe to resynchronise.
integer
The Fogo slot the template was built at.
object
The same payload POST /cpi/route-accounts returns: the ready routeInstruction, requiredTokenAccounts, referrerAta, and addressLookupTableAddresses.
Amounts inside route are as-of the last template change, not tick-fresh. Re-quote (or redeem a firm quoteId) if you need the current price at execution time.

Invalidate push

Sent when the last quote pushed for a pair drifts past the firm margin before a fresh quote push replaces it — the firm window on that quoteId is effectively dead, so don’t redeem it with firm: true. This is best-effort UX sugar: the redemption-time check on the server is the real gate, so you may still occasionally get a 409 without having seen an invalidate.
string
Always "invalidate".
string
The commitment ID that drifted stale.

Acknowledgement

Sent in response to a subscribe / unsubscribe, or to report a problem with a message.
string
"subscribed", "unsubscribed", or "error".
string
Present on errors — e.g. "invalid json", "unknown op".
integer
Number of pairs accepted by a subscribe/unsubscribe. Invalid or duplicate pairs are skipped and not counted.

Examples

Limits & behavior

If your client can’t keep up with the push rate, the server drops the connection rather than buffering unboundedly. Read frames promptly, and on an unexpected close, reconnect and re-send your subscriptions.
Last modified on August 31, 2026