Getting started

Authentication

A single key opens the three channels. It travels differently on each one (a header in REST and in MCP, a URL parameter at the handshake in WebSocket), but its value is the same everywhere, and it is the rights of your plan that apply on each.

#REST API

The key goes in the X-API-KEY header. No URL parameter, no Authorization: Bearer: a key in a query string would end up in the access logs of every intermediary along the way.

curl
curl -H "X-API-KEY: $BYTNODE_KEY" \
  "https://api.bytnode.com/v1/basis?symbol=ETHUSDT"

The comparison runs in constant time, so a wrong key takes exactly as long to be rejected as an almost-right one.

#The route without a key

Only one route answers without authentication: /v1/health, which says whether the service is up. The two routes you call right after (the freshness of the data and the asset catalogue) require the key, like everything else. /v1/status is a probe: never counted against the monthly quota, but subject to the rate limit of the plan. /v1/symbols belongs to the price and volume family, open on every plan, and counts like any other call.

GET/v1/healthno key
GET/v1/status
GET/v1/symbols
curl
# The only route that answers without a key.
curl https://api.bytnode.com/v1/health

# The next two require the key. /v1/status is a probe: never counted
# against the monthly quota, but subject to the rate limit of the plan.
curl -H "X-API-KEY: $BYTNODE_KEY" https://api.bytnode.com/v1/status
curl -H "X-API-KEY: $BYTNODE_KEY" https://api.bytnode.com/v1/symbols

#WebSocket stream

The key is presented in the URL, as the ?api_key= parameter, and it is checked at the handshake, before the connection opens. A browser cannot set a header on a WebSocket: this is the only form that works everywhere, and it is the one the examples use.

import asyncio
import json
import os

import websockets


async def main() -> None:
    # The key travels in the URL, at the handshake: the connection opens
    # already authenticated. A missing or invalid key gets 403 before it opens.
    url = f"wss://api.bytnode.com/ws?api_key={os.environ['BYTNODE_KEY']}"
    async with websockets.connect(url) as ws:
        await ws.send(json.dumps({
            "op": "subscribe",
            "channels": ["trades.futures.BTCUSDT"],
        }))
        print(await ws.recv())   # {"op": "subscribed", "channels": [...]}


asyncio.run(main())

The session opens already authenticated: the first message can be the subscription. A key that is missing, invalid or revoked, or a plan that does not include the stream, gets a 403 at the handshake and nothing is established. The plan’s ceiling on simultaneous connections applies to the whole account: the connection too many is closed with code 4003. The old frame {"op": "auth", "api_key": "…"} is still accepted and answers ok, but it is optional.

#MCP server

The MCP server expects the same key, in the same X-API-KEY header, set on the HTTP connection. Without it, no tool is exposed: the server refuses by default rather than opening by default.

from fastmcp import Client

headers = {"X-API-KEY": "your_key"}

async with Client("https://mcp.bytnode.com", headers=headers) as client:
    info = await client.call_tool("get_system_info", {})

The exact shape of the configuration file depends on the agent; the principle does not change: a URL and a header. See MCP server.

#What a refusal says

ChannelResponseCause
REST403, X-Deny-Reason: keyHeader missing, key unknown or revoked. The body says « Cle API absente, invalide ou revoquee ».
REST403, X-Deny-Reason: familyThe key is valid, but the family of this endpoint is not in your plan.
MCP401, header X-API-KEY requisHeader missing on the connection, or key refused. The check happens before a single tool is announced; after that, every tool call is judged against the rights of the key.
WebSocket403 at the handshakeKey missing or invalid in the URL (key), or plan without the stream (websocket). The connection does not open.
WebSocketclose 4003The plan’s ceiling on simultaneous connections reached, for the whole account. The reason gives the ceiling.

#Protecting and rotating the key

Where to keep it

  • In an environment variable or a secrets manager, never in the code repository.
  • Server-side only. A key placed in page JavaScript is public, whatever the obfuscation.
  • One key per environment: development, staging, production. A leak can then be revoked without stopping the rest.

Rotating it

  • Create the new key in the console. Both stay valid during the overlap.
  • Deploy it everywhere, then check that traffic on the old one has fallen back to zero.
  • Revoke the old one. Revocation is immediate.