Autenticación
Una sola clave abre los tres canales. Viaja de forma distinta en cada uno (una cabecera en REST y en MCP, un parámetro de URL en el handshake de WebSocket), pero su valor es el mismo en todas partes, y en cada uno se aplican los permisos de tu plan.
#API REST
La clave va en la cabecera X-API-KEY. Ni parámetro de URL ni Authorization: Bearer: una clave en una query string acabaría en los registros de acceso de cada intermediario del camino.
curl -H "X-API-KEY: $BYTNODE_KEY" \
"https://api.bytnode.com/v1/basis?symbol=ETHUSDT"La comparación se ejecuta en tiempo constante, así que una clave errónea tarda exactamente lo mismo en rechazarse que una casi correcta.
#La ruta sin clave
Solo una ruta responde sin autenticación: /v1/health, que indica si el servicio está en marcha. Las dos rutas que se llaman justo después (la frescura de los datos y el catálogo de activos) exigen la clave, como todo lo demás. /v1/status es una sonda: nunca se descuenta de la cuota mensual, pero está sujeta al límite de velocidad del plan. /v1/symbols pertenece a la familia precio y volumen, abierta en todos los planes, y cuenta como cualquier otra llamada.
# 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#Flujo WebSocket
La clave se presenta en la URL, como parámetro ?api_key=, y se comprueba en el handshake, antes de que se abra la conexión. Un navegador no puede poner cabeceras en un WebSocket: es la única forma que funciona en todas partes, y la que usan los ejemplos.
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())La sesión se abre ya autenticada: el primer mensaje puede ser la suscripción. Una clave ausente, inválida o revocada, o un plan que no incluye el flujo, recibe un 403 en el handshake y no se establece nada. El límite de conexiones simultáneas del plan se aplica a toda la cuenta: la conexión que sobra se cierra con el código 4003. La antigua trama {"op": "auth", "api_key": "…"} se sigue aceptando y responde ok, pero es opcional.
#Servidor MCP
El servidor MCP espera la misma clave, en la misma cabecera X-API-KEY, puesta en la conexión HTTP. Sin ella no se expone ninguna herramienta: el servidor rechaza por defecto en lugar de abrir por defecto.
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", {})La forma exacta del archivo de configuración depende del agente; el principio no cambia: una URL y una cabecera. Ver Servidor MCP.
#Lo que dice un rechazo
| Canal | Respuesta | Causa |
|---|---|---|
| REST | 403, X-Deny-Reason: key | Cabecera ausente, clave desconocida o revocada. El cuerpo dice « Cle API absente, invalide ou revoquee ». |
| REST | 403, X-Deny-Reason: family | La clave es válida, pero la familia de este endpoint no está en tu plan. |
| MCP | 401, header X-API-KEY requis | Cabecera ausente en la conexión, o clave rechazada. La comprobación ocurre antes de anunciar una sola herramienta; después, cada llamada de herramienta se evalúa según los permisos de la clave. |
| WebSocket | 403 en el handshake | Clave ausente o inválida en la URL (key), o plan sin el flujo (websocket). La conexión no se abre. |
| WebSocket | cierre 4003 | Se alcanzó el límite de conexiones simultáneas del plan, para toda la cuenta. El motivo indica el límite. |
#Proteger y rotar la clave
Dónde guardarla
- En una variable de entorno o un gestor de secretos, nunca en el repositorio de código.
- Solo en el servidor. Una clave puesta en el JavaScript de una página es pública, sea cual sea la ofuscación.
- Una clave por entorno: desarrollo, preproducción, producción. Así una filtración puede revocarse sin detener el resto.
Rotarla
- Crea la nueva clave en la consola. Ambas siguen siendo válidas durante el solapamiento.
- Despliégala en todas partes y comprueba que el tráfico de la antigua ha caído a cero.
- Revoca la antigua. La revocación es inmediata.