Realtime Streaming
You can stream realtime market data via WebSocket if you have subscribed to the enterprise package.
The v2 WebSocket streams realtime market data by exchange: traded prices and bid/ask
quotes, for single instruments or a whole exchange with one subscription.
1. Connect
wscat -c "wss://ws.bavest.co/v2" -H "x-api-key: <API KEY>"Your API key is validated during the handshake. An invalid or missing key rejects the
connection before it opens.
2. Topics
Everything you can subscribe to is a topic:
<channel>.<MIC>.<ISIN> e.g. trades.XMUN.DE0007164600
<channel>.<MIC>.* e.g. quotes.XMUN.* (every instrument on the exchange)
| Part | Meaning |
|---|---|
channel | trades = traded prices. quotes = bid/ask. |
MIC | ISO-10383 operating MIC of the exchange. Currently enabled: XMUN (Börse München / gettex). |
ISIN | 12-character ISIN, or * to receive every instrument on the exchange. |
Subscribing to an exchange that is not enabled returns an error frame per topic, e.g.
exchange XETR is not enabled.
3. Actions
{"action": "subscribe", "topics": ["trades.XMUN.DE0007164600", "quotes.XMUN.*"]}
{"action": "unsubscribe", "topics": ["quotes.XMUN.*"]}
{"action": "ping"}The server acknowledges with {"ev":"subscribed", ...} / {"ev":"unsubscribed", ...} /
{"ev":"pong"}. Invalid topics in a subscribe request are rejected individually with an
error frame; the valid ones still subscribe.
4. Messages
Every server frame carries an ev field. Data frames may arrive batched as a JSON
array (one WebSocket message containing many ticks); control frames (acks, pong,
errors, snapshots) are single objects. Your client must handle both shapes.
Trade (ev: "T", channel trades)
ev: "T", channel trades)Every trade is delivered; trades are never dropped or merged.
{"ev": "T", "topic": "trades.XMUN.DE0007164600", "x": "XMUN",
"isin": "DE0007164600", "p": 123.45, "v": 100, "c": "EUR", "t": 1764236768617}| Field | Description |
|---|---|
p | Traded price |
v | Trade volume (optional) |
c | Currency |
t | Event time, epoch milliseconds UTC |
x | Exchange MIC |
Quote (ev: "Q", channel quotes)
ev: "Q", channel quotes)Bid/ask updates, conflated to roughly one message per instrument per second under load
(you always receive the newest state; superseded intermediate quotes are dropped).
{"ev": "Q", "topic": "quotes.XMUN.DE0007164600", "x": "XMUN",
"isin": "DE0007164600", "bp": 123.40, "bs": 200, "ap": 123.45, "as": 100,
"c": "EUR", "t": 1764236768617}| Field | Description |
|---|---|
bp / bs | Bid price / bid size |
ap / as | Ask price / ask size |
Snapshot
Immediately after subscribing to an exact-ISIN topic you receive the last known value
with "snap": true, so you can render current state before the first live tick arrives.
Errors
{"ev": "error", "code": "exchange_not_enabled", "topic": "quotes.XETR.DE0007164600",
"message": "exchange XETR is not enabled"}| Code | Meaning |
|---|---|
bad_request | Message was not valid JSON or had no usable action/topics |
bad_topic | Topic does not match <channel>.<MIC>.<ISIN or *> |
exchange_not_enabled | The MIC is not available on your stream |
too_many_topics | Per-connection topic limit reached |
5. Connection lifecycle, heartbeats and reconnects
- Keep-alive: the connection closes after 10 minutes of inactivity. Send
{"action":"ping"}every 30 to 60 seconds; the server replies{"ev":"pong"}. - Maximum session length: 2 hours (platform limit). Your client must expect the
socket to close and reconnect. - Reconnect best practice: reconnect with exponential backoff and jitter (start at
1s, double up to a 30s cap, add random jitter). After reconnecting, resubscribe to your
topics; the snapshots you receive bring you back to current state. Do not reconnect in
a tight loop: connections are capped per API key, and a reconnect storm will be
rejected at the handshake. - Subscription changes become effective within a few seconds. The subscribe snapshot
reflects current state immediately.
6. Limits
| Limit | Value |
|---|---|
| Topics per connection | 200 |
| Concurrent connections per API key | 20 |
| Max message size (server to client) | 128 KB (data is chunked below this) |
7. Quickstart (Python)
import asyncio, json, random
import websockets
URL = "wss://ws.bavest.co/v2"
TOPICS = ["trades.XMUN.*", "quotes.XMUN.DE0007164600"]
async def stream():
backoff = 1
while True:
try:
async with websockets.connect(
URL, extra_headers={"x-api-key": "<API KEY>"}
) as ws:
backoff = 1 # reset after a successful connect
await ws.send(json.dumps({"action": "subscribe", "topics": TOPICS}))
async def keepalive():
while True:
await asyncio.sleep(30)
await ws.send(json.dumps({"action": "ping"}))
asyncio.create_task(keepalive())
async for message in ws:
frames = json.loads(message)
for frame in frames if isinstance(frames, list) else [frames]:
if frame.get("ev") == "T":
print("trade", frame["isin"], frame["p"])
elif frame.get("ev") == "Q":
print("quote", frame["isin"], frame["bp"], frame["ap"])
except Exception:
await asyncio.sleep(backoff + random.random())
backoff = min(backoff * 2, 30)
asyncio.run(stream())