Self-Service Access
There are two ways to reach the CryptoStruct realtime market data feed. Managed access is what Accessing Data describes: you run a Backend installation, look up hosts through the master data API, and your server's IP is whitelisted for the markets you use.
Self-service access needs none of that. You buy access in the shop, get an API key, and connect to a fixed endpoint. This page covers what is different — the wire protocol itself is the same one documented in Protocol.
Note
Self-service access is currently in public beta. The feed and the protocol are the production platform behind the managed feeds; the self-service path — shop, API keys and per-day access — is the new part.
What differs from managed access
Getting access
Realtime access is sold in the shop per UTC calendar day, either as a whole market (every instrument of that market) or as single instruments.
-
Buy days. Pick a market or a set of instruments and a duration at cryptostruct.com/shop. Paid days always start on the next UTC day, and the remainder of the purchase day is included free — your key validates the moment the order is paid. Rates are on the pricing page.
-
Get your API key. It is created with your first realtime purchase and shown at cryptostruct.com/account/realtime, together with your current and upcoming days. There is one key per account, and you can rotate it there at any time — rotation does not affect the days you already bought.
-
Look up instrument IDs. Subscriptions take numeric instrument IDs. Fetch them from the master data export described below.
A day ends at 00:00 UTC. Extend before that if you want to stay connected across midnight.
Endpoints
Each market listens on its own port. There is no market switch inside the
protocol and no discovery endpoint — pick the URL of the market you
bought and append your key as ?apiKey=….
The encoding is selected by path: /api/v6 is JSON over text frames and
the place to start, /api/v6/sbe is SBE over binary frames for
low-latency clients. A complete URL looks like this:
ws://lon1.cryptostruct.com:14004/api/v6?apiKey=<your key>
lon1 is the delivery endpoint, not the source: the data is captured at
the venue in Tokyo and delivered at our London endpoint. Further
locations are available through managed access.
Warning
The self-service endpoints are plain ws:// today — there is no TLS in
front of the proxy. Run your client from a server you control, and keep
the key out of browser-side code.
Checking an endpoint
The /api/info endpoint of a port is plain HTTP and needs no key:
http://lon1.cryptostruct.com:14004/api/info?all=true
It answers with the proxy's process ID, the protocol version, both
endpoints and the capabilities per topic — the same object the login
response carries. Capabilities differ per market: spot markets have no
mark price, index price, funding rate or liquidations, so those topics
are null there and never emit. USDT-M perpetuals have all seven topics.
Instrument master data
Self-service access has no Backend installation and therefore no
/api/exchanges or /api/instruments. The equivalent is a per-market
export of every sellable instrument, served by the shop:
GET https://cryptostruct.com/api/realtime/instruments?market=<code>
GET https://cryptostruct.com/api/realtime/instruments?market=<code>&format=csv
<code> is the market code from the endpoint table. Authenticate with
your realtime API key as an Authorization: Bearer header — the same key
as the feed, but sent as a header here, never as a URL parameter.
curl -sS -H "Authorization: Bearer $CRYPTOSTRUCT_REALTIME_API_KEY" \
"https://cryptostruct.com/api/realtime/instruments?market=binance_swap&format=csv" \
-o realtime_instruments_binance_swap.csv
The JSON form is an envelope
{market: {exchange_id, code, name}, generated_at, count, instruments: [...]}
with the fields below per row. The CSV form carries one header line with
the same columns in the same order.
The decimal columns are strings. Keep them as strings or parse them
with a decimal type, never a float. Absent values are null in JSON
and empty in CSV.
Only open and review instruments are listed — the sellable set. The
list is refreshed server-side about hourly; fetch it at start-up and
re-fetch daily, not per message.
Connecting
The server sends nothing until you log in and subscribe. Every session is the same sequence:
-
Connect to the endpoint of your market, with your key in
?apiKey=. -
Send the login
[13, organization, application_name, application_version, process_id]and read the[14, …]response with the capabilities. The four strings are free-form and used for logging only — authentication already happened through the key in the URL. -
Send
[11, instrumentId]per instrument. The server answers with a[5, id, ts, "READY", ""]state event and the current state, then streams live updates. -
Keep reading. The server pings every 5 s; standard WebSocket libraries answer with a pong by themselves.
The optional third element of the subscribe message selects topics; the defaults are listed under Subscription. Liquidations are off by default and have to be requested explicitly.
A smoke test without writing code, using websocat — connect, then paste the two lines:
websocat -t 'ws://lon1.cryptostruct.com:14004/api/v6?apiKey=<your key>'
[13,"my-org","websocat","1.0.0","smoke"]
[11,67824]
The same in Python:
# pip install websockets
import asyncio, json, websockets
URL = "ws://lon1.cryptostruct.com:14004/api/v6?apiKey=<your key>"
INSTRUMENT = 67824 # BTCUSDT
async def recv(ws):
raw = await ws.recv()
if not isinstance(raw, str) or not raw.startswith("["):
# plain-text server notice sent right before a close
raise RuntimeError(f"server: {raw!r}")
return json.loads(raw)
async def main():
try:
async with websockets.connect(URL, max_size=None) as ws:
await ws.send(json.dumps([13, "my-org", "my-client", "1.0.0", "client-01"]))
login = await recv(ws)
print("logged in:", login[1], "capabilities:", sorted(login[4]))
await ws.send(json.dumps([11, INSTRUMENT]))
while True:
msg = await recv(ws)
print(msg[0], msg[1])
except websockets.exceptions.InvalidHandshake as e:
# HTTP 401 — unknown key, or no paid day for this market today
print("handshake rejected:", e)
asyncio.run(main())
Tip
The payload of every message type, the topic flags and the capability fields are documented in Protocol; the binary layout is in SBE Encoding. Prices, quantities, event IDs and trade IDs are decimal strings, and timestamps are nanoseconds since the Unix epoch.
Keep-alive, errors and reconnecting
Heartbeat
The server sends a WebSocket ping every 5 s and closes the connection if
no pong arrives within 30 s (pong check failed). Standard libraries
answer pings automatically — you only have to keep reading from the
socket.
Rejected at the handshake
An unknown key, or a key without a paid day for the market behind that
port, never gets a WebSocket. The upgrade request is answered with HTTP
401 and a text/plain body:
api key rejected: <reason>
Client libraries surface this as a failed connection rather than a
WebSocket close: Python websockets raises InvalidHandshake, Node ws
emits unexpected-response with the status and the body. The second
reason is the common case — verify the port you connected to and your
days at
/account/realtime. Do not
retry a rejected handshake in a tight loop.
Notices and instrument errors
Connection-level notices arrive as a plain-text frame — not a JSON array
— immediately before the close, on a running session only:
api key expired or revoked, not logged in, already logged in,
protocol violation: <detail>, pong check failed, service shutdown.
Log them verbatim; they say why the connection ended.
Instrument-level problems come as state events
[5, id, ts, "ERROR", "…"] and never drop the connection:
instrument not available on this endpoint,
instrument not permitted for this api key,
instrument permission expired or revoked and
instrument unsubscribed. A whole-market day unlocks every instrument of
that market; an instrument day unlocks exactly the IDs you bought.
Connection limit
Each key may hold a limited number of parallel connections per market — five by default. The current value is shown on your access card. Further handshakes are refused.
Reconnecting
Connect, log in, subscribe again. Every subscribe starts with a fresh
snapshot, so no state is lost — do not try to resume by event ID. The
same applies mid-session: on a prevEventId gap, or a
snapshot with forceReset, unsubscribe and
resubscribe the instrument and rebuild the book from the new snapshot.
When access changes take effect
Days and instruments you buy apply at your next login, so reconnect after
a purchase. A rotated key or a refund is enforced at the next login and
by the proxy's periodic re-check, which runs about hourly. Expect the
notice api key expired or revoked followed by a close at 00:00 UTC when
the next day is not bought — this is the expected end of an unextended
day.
Security notes
-
On the WebSocket the key travels in the URL query string. Treat the full URL like a password: keep it out of shared configs, tickets, logs and screenshots.
-
On the master data route the key is an
Authorization: Bearerheader and must never be put into that URL, so it stays out of access logs. -
Rotate the key at /account/realtime if it ever leaks. The old key stops validating at the next login and re-check; your purchased days are unaffected.
-
One key per account is the model. Do not share a key between systems you may want to cut off separately — use the connection limit for parallelism instead.
See Also
-
Accessing Data. Managed access: master data API, host discovery and IP whitelisting.
-
Protocol. Market data protocol specification.
-
SBE Encoding. Simple Binary Encoding for market data.