Channels & payloads
| Channel | Rate | Content |
|---|---|---|
ticker:<SYMBOL> | real-time | Best bid/ask per exchange for one pair, e.g. ticker:BTCUSDT |
agg:* | up to every 1 s | Cross-exchange summary for all pairs |
Symbols are uppercase with no separator (BTCUSDT). btc-usdt and BTC_USDT are accepted too. Messages are only sent when something changed.
ticker:<SYMBOL>
{
"type": "ticker",
"ts": 1731400000140,
"channel": "ticker:BTCUSDT",
"s": "BTCUSDT",
"data": [
{ "ex": "binance", "b": 97012.5, "a": 97012.51, "bq": 3.214, "aq": 0.82, "t": 1731400000123 },
{ "ex": "okx", "b": 97011.9, "a": 97012.0, "bq": 0.51, "aq": 1.07, "t": 1731400000098, "et": 1731399999970 }
]
}ticker:BTCUSDT- Waiting for messages…
ts is the server time when the message was built; t on each quote is when that quote arrived from the exchange (see timestamps and the full message reference). A ticker message contains only the exchanges that changed. Tickers are real-time: a message is sent every time an exchange's top of book changes, so a busy pair like BTCUSDT can produce dozens of messages per second. A snapshot for the same channel has the same shape with every exchange.
| Field | Type | Description |
|---|---|---|
ex | string | Exchange id: binance, bybit, okx, mexc, gateio, kucoin, bitget, coinbase, kraken, bitfinex, upbit, cryptocom |
b | number | Best bid price |
a | number | Best ask price |
bq | number | Size at best bid (base asset) |
aq | number | Size at best ask (base asset) |
t | number | Receive time: when the update arrived from the exchange (ms since Unix epoch) |
et | number, optional | Exchange event time (ms), as reported by the exchange. t - et ≈ latency. Omitted for Binance, Bitfinex and Gate. |
Every exchange speaks a different dialect: positional arrays, protobuf, bidPr vs best_bid, BTC-USDT vs BTC/USDT. Aeon Feed normalises all of them into this one shape. Each exchange page shows that exchange's native message next to the normalised result.
The mid price is (b + a) / 2. Treat quotes older than about 30 seconds as stale. A quote that has not changed is re-sent at least every 10 seconds with a fresh t while its exchange is still live, so a quiet book never looks stale by mistake. Compare t with server time, not your local clock: the welcome message carries the server time t, so you can compute the offset once.
agg:*
{
"type": "agg",
"ts": 1731400001000,
"channel": "agg:*",
"data": [
{ "s": "BTCUSDT", "p": 97012.3, "bb": 97014.0, "bbx": "kraken", "bbq": 0.42, "ba": 97011.8, "bax": "bybit", "baq": 1.37,
"sp": -0.0023, "n": 11, "ch": 1.84, "v": 1834455120, "t": 1731400000123 }
]
}| Field | Type | Description |
|---|---|---|
s | string | Symbol |
p | number | Reference price: the median of exchange mid prices |
bb / bbx | number / string | Highest bid across exchanges, and the exchange that has it |
ba / bax | number / string | Lowest ask across exchanges, and the exchange that has it |
bbq / baq | number | Size (base asset) available at bb on bbx / at ba on bax. Multiply by the price for the USD(T) value. |
sp | number | Cross-exchange spread in % of p: (ba - bb) / p × 100. Negative means the books are crossed. |
n | number | Number of exchanges with a fresh quote (< 30 s old). 0 means the pair is currently unavailable: remove it from your state. |
ch | number? | 24h change in %, against the 24h open from a reference exchange (Binance, falling back to OKX) |
v | number? | 24h quote volume in USDT on the reference exchange |
t | number | Latest update time among the included quotes |
agg:*- Waiting for messages…
Like ticker, an agg message only contains pairs whose values changed. The snapshot contains all of them.
Coverage
Not every exchange lists every pair. The server checks each (exchange, pair) at startup and every 6 hours, and only streams pairs that exist. New listings are picked up automatically. GET /v1/pairs shows the exchanges for each pair.
Coinbase and Kraken have thinner USDT order books than their USD books, so their quotes can be a few basis points away from the rest.