Live stream
An SSE stream over the same tape /v1/feed pages through. Use it when you want trades pushed to a process you are running; use webhooks when you want them delivered to a URL you own.
Authenticate with the usual bearer header. Browser EventSource cannot set headers, so ?key=rivo_live_... is accepted as well. Prefer the header wherever your client allows it: a key in a query string ends up in proxy and server logs.
The stream opens with a `: connected` comment and sends a `: ping` comment every 25 seconds. Both are SSE comments, not events, and clients should ignore them. Each event arrives as a `data:` line holding one JSON trade in the same shape /v1/feed returns.
A key may hold three concurrent streams. A fourth is refused with 429. Connection time is charged against the rate limit at one request per minute, so an hour of streaming costs 60 of your 120-per-minute budget spread across that hour rather than a single request.
Reconnect on disconnect with your own backoff. There is no replay: events missed while disconnected are gone, so use /v1/feed with a cursor to backfill a gap.
Query parameters
lanestringdefault: whale"whale" for large trades regardless of who placed them, "tracked" for any trade by an identified trader regardless of size.
keystringYour API key, for EventSource clients that cannot set an Authorization header. Omit when sending the bearer header.
Response
Content-Type is text/event-stream, not application/json, and the body never ends. Do not await the full response.
Frames carry the same venue-identifier rules as every other endpoint: contract ids appear only for a key holding the trade scope.
curl -N "https://api.rivo.markets/v1/stream?lane=tracked" \
-H "Authorization: Bearer rivo_live_..."import json
import httpx
url = "https://api.rivo.markets/v1/stream"
headers = {"Authorization": "Bearer rivo_live_..."}
# stream=True, and never call .json(): the body does not end.
with httpx.stream("GET", url, headers=headers, params={"lane": "tracked"}, timeout=None) as r:
r.raise_for_status()
for line in r.iter_lines():
if line.startswith(":"): # ": connected" / ": ping" keepalives
continue
if line.startswith("data: "):
trade = json.loads(line[6:])
print(trade["marketTitle"], trade["amount"])// EventSource cannot set headers, so it takes the key in the query string.
// Prefer the fetch form below wherever your runtime allows it.
const es = new EventSource(
"https://api.rivo.markets/v1/stream?lane=tracked&key=rivo_live_...",
);
es.onmessage = (e) => {
const trade = JSON.parse(e.data);
console.log(trade.marketTitle, trade.amount);
};
es.onerror = () => {
// The browser reconnects on its own. There is no replay: backfill any gap
// from /v1/feed with a cursor.
};
// Server-side, keep the key in a header instead:
const res = await fetch("https://api.rivo.markets/v1/stream?lane=tracked", {
headers: { Authorization: "Bearer rivo_live_..." },
});
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
for (const line of (value ?? "").split("\n")) {
if (line.startsWith("data: ")) console.log(JSON.parse(line.slice(6)));
}
}: connected
data: {"id":"b4a4...1c77","platform":"polymarket","marketTitle":"Will the Fed cut in September?","outcomeName":"YES","direction":"buy_yes","amount":24500,"price":0.62,"traderName":"coldbrew","detectedAt":"2026-08-13T07:58:03.000Z"}
: ping
data: {"id":"c7f1...92ab","platform":"kalshi","marketTitle":"Highest temperature in NYC today","outcomeName":"NO","direction":"buy_no","amount":8100,"price":0.44,"traderName":"riverboat","detectedAt":"2026-08-13T07:58:41.000Z"}