You need a plan that streams and its API key, which is on Live Streams once you are signed in. Each example streams BTC from ohlcv.binance-futures.1m until you press Ctrl+C.
import os
from aperiodic import stream
for message in stream(
api_key=os.environ["APERIODIC_API_KEY"],
dataset="ohlcv",
exchange="binance-futures",
interval="1m",
symbols=["perpetual-BTC-USDT:USDT"], # omit for every symbol on your plan
):
print(message.channel, message.snapshot, message.data)The Python client and the CLI reconnect and resubscribe on their own.
export APERIODIC_API_KEY='<your key>'
aperiodic stream ohlcv --exchange binance-futures --interval 1m --symbols perpetual-BTC-USDT:USDTAny WebSocket client works. With wscat, type the second line once connected:
wscat -c "wss://stream.aperiodic.io/v1/stream" \
-H "X-API-KEY: $APERIODIC_API_KEY"
> {"op":"subscribe","id":"c1","channels":[{"dataset":"ohlcv","exchange":"binance-futures","interval":"1m","symbols":["perpetual-BTC-USDT:USDT"]}]}import asyncio, json, os
import websockets
async def main():
async with websockets.connect(
"wss://stream.aperiodic.io/v1/stream",
additional_headers={"X-API-KEY": os.environ["APERIODIC_API_KEY"]},
) as ws:
await ws.send(json.dumps({"op":"subscribe","id":"c1","channels":[{"dataset":"ohlcv","exchange":"binance-futures","interval":"1m","symbols":["perpetual-BTC-USDT:USDT"]}]}))
async for message in ws:
frame = json.loads(message)
if "channel" in frame:
print(frame["channel"], frame["data"])
elif frame.get("op") in ("subscribed", "unsubscribed", "error"):
print(frame)
asyncio.run(main())Open a WebSocket to wss://stream.aperiodic.io/v1/stream with your API key in the X-API-KEY header. The key is never accepted in the URL, where it would end up in logs and proxies. Browsers cannot set headers on a WebSocket, so the feed is for servers, notebooks and scripts, not for web pages.
The key is checked before the upgrade, so a refused connection gets an ordinary HTTP status with a JSON error message instead of a socket:
| Status | Meaning |
|---|---|
| 401 | No X-API-KEY header, or the key is not valid. |
| 403 | The key is valid, but its plan does not stream. Every plan on sale does: upgrade to stream. |
| 426 | The request was not a WebSocket upgrade. |
| 429 | Your account already holds as many connections as its plan allows. Close one, or wait for a dropped one to expire (see Limits). |
| 503 | Streaming is temporarily unavailable. Retry with backoff. |
A new plan or a new key reaches the feed within 60 seconds. If a connection is refused right after you upgrade, wait a minute and try again.
A channel is one dataset on one exchange at one interval, named dataset.exchange.interval with the catalog's names: ohlcv.binance-futures.1m, open_interest.okx-perps.1m. Symbols are Atlas IDs, the same as in the REST API: perpetual-BTC-USDT:USDT. Rows arrive when each bar closes, so a 1h channel sends once an hour.
These datasets stream. Intervals 15s and 30s are Institutional only; every other plan gets the rest of each list.
| Dataset | Exchanges | Intervals | From |
|---|---|---|---|
| flow Flow Metrics | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| trade_size Trade Size | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| range Price Range & Distribution | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| impact Impact Metrics | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| updownticks Up / Down Tick Metrics | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| run_structure Trade Run Structure | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| returns Returns & Volatility | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| slippage Slippage | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| basis Basis | binance-futures, okx-perps, hyperliquid-perps | 1m 5m 15m 30m 1h 4h 1d | Pro |
| funding Funding | binance-futures, okx-perps, hyperliquid-perps | 1m 5m 15m 30m 1h 4h 1d | Pro |
| open_interest Open Interest | binance-futures, okx-perps, hyperliquid-perps | 1m 5m 15m 30m 1h 4h 1d | Pro |
| derivative_price Derivative Price | binance-futures, okx-perps, hyperliquid-perps | 1m 5m 15m 30m 1h 4h 1d | Pro |
| ohlcv Candlesticks | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| vtwap VWAP / TWAP | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| l1_price L1 Price | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Pro |
| l1_imbalance L1 Imbalance | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Prime |
| l1_liquidity L1 Liquidity | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Prime |
| l2_imbalance L2 Order Book Imbalance | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Prime |
| l2_liquidity L2 Order Book Liquidity | binance-futures, okx-perps, hyperliquid-perps | 15s 30s 1m 5m 15m 30m 1h 4h 1d | Prime |
Every frame is a JSON text frame. Your frames carry an op and an optional id matching ^[A-Za-z0-9_\-.:]{1,64}$, which the answer echoes.
A channel is a name string, or an object with dataset, exchange, interval and an optional symbols list. Leave out symbols for every symbol, where your plan allows it. Subscribing to a channel you already have adds the new symbols to it. Put all your channels in one frame, up to 200, rather than one frame each.
{"op":"subscribe","id":"c1","channels":[{"dataset":"ohlcv","exchange":"binance-futures","interval":"1m","symbols":["perpetual-BTC-USDT:USDT","perpetual-ETH-USDT:USDT"]},"open_interest.okx-perps.1m"]}Drop a whole channel, or some of its symbols. Symbols can only be dropped from a channel subscribed with a symbol list: to narrow an all-symbols subscription, unsubscribe the channel and subscribe again with the symbols you want.
{"op":"unsubscribe","id":"u1","channels":["ohlcv.binance-futures.1m"]}
{"op":"unsubscribe","id":"u2","channels":[{"dataset":"ohlcv","exchange":"binance-futures","interval":"1m","symbols":["perpetual-ETH-USDT:USDT"]}]}{"op":"ping"} is answered with {"op":"pong"}, echoing the ping's id if it has one. You do not need to ping: the server sends heartbeats on an idle connection.
Data. One row, exactly as it was produced. Its fields are the dataset’s columns, listed on its catalog page.
{"channel":"ohlcv.binance-futures.1m","data":{…}}Snapshot. Right after a subscribe, on plans with snapshots: the latest row per symbol, so you need not wait for the next bar. Best-effort.
{"channel":"ohlcv.binance-futures.1m","snapshot":true,"data":{…}}subscribed. The answer to a subscribe: what was granted, and why anything was refused. A frame can be partly granted.
{"op":"subscribed","id":"c1","channels":["ohlcv.binance-futures.1m"],"rejected":[{"channel":"open_interest.okx-perps.1m","code":"not_entitled","message":"Dataset open_interest is not included in your plan"}]}unsubscribed. The answer to an unsubscribe. Also sent unprompted, without an id, when a plan change removes channels.
{"op":"unsubscribed","id":"u1","channels":["ohlcv.binance-futures.1m"],"rejected":[]}pong. The answer to a ping, echoing its id if it had one.
{"op":"pong","id":"p1"}heartbeat. Sent when a connection has received nothing for 25 seconds. time is in epoch milliseconds.
{"op":"heartbeat","time":1791210600000}error. A message that could not be applied, with a code from the table under Errors. The connection stays open.
{"op":"error","code":"limit_exceeded","message":"At most 10 control frames per second"}Rows are keyed by exchange, symbol, interval, timestamp type and time. A row you receive twice carries the same key, so overwrite rather than append.
Limits per plan. Connections count across your whole account.
| Plan | Connections | Channels per connection | Symbols per channel | Snapshots |
|---|---|---|---|---|
| Pro | 5 | 50 | Up to 100, listed | Yes |
| Prime, Prime Commercial, Prime + Raw | 10 | 200 | All | Yes |
| Institutional | 25 | Up to 1,000 | All | Yes |
On a plan with a symbol limit, every subscribe has to list its symbols: one without symbols is refused with limit_exceeded.
On every plan, one connection may:
error frame with code limit_exceeded and are not applied: pause and send them again. A connection is closed only if it stays over the limit for 10 seconds straight.Close sockets cleanly so their slot frees at once. A connection that disappears without a close keeps its slot for up to 2 minutes, and a reconnect in that time can be refused with 429 if you are at your plan's limit.
A subscribe or unsubscribe that is partly refused lists each refused channel under rejected with one of these codes. The same codes appear in error frames.
| Code | Meaning |
|---|---|
| not_entitled | The dataset or interval is not in your plan. |
| not_live | Your plan has the dataset, but it is not streamed at that interval. The message names the intervals it is streamed at. |
| unknown_channel | The channel is malformed, or the exchange does not carry that dataset. |
| limit_exceeded | A plan or connection limit would be passed. The message says which. |
| invalid_message | The frame could not be applied: not JSON, a missing field, or unsubscribing from something you are not subscribed to. |
The server closes a connection with one of these codes:
| Close code | Meaning |
|---|---|
| 4001 | Your plan lapsed, the key was rotated, or your entitlement could not be confirmed for 15 minutes past its expiry. An error frame with code auth_expired comes first. Reconnecting with the same key will be refused until the plan or key is fixed. |
| 1008 | The connection kept sending control messages over the limit for 10 seconds straight. |
| 1011 | The server lost the connection’s state. Reconnect and subscribe again. |
| 1006 | Never sent by the server: your client reports it when the connection dropped without a close frame, for example on a network change or a server restart. Reconnect. |
unsubscribed frame. When a plan lapses or its key is rotated, the connection closes with 4001. An upgrade reaches open connections at the next check; reconnect to use it sooner.X-API-KEY header. Connect from a server and relay to your pages.Questions or a limit that does not fit your use: support@aperiodic.io.