Autenticación y Control de Acceso

Toda solicitud /v1 se autentica mediante clave de API. La clave puede además restringirse por alcance, por IP de origen y por firma de la solicitud.

ᚱ
"Heimdall conoce el nombre, la hora y el propósito de cada viajero que pisa el puente. Quien no sepa decir los tres no cruza."

Clave de API

Envía tu clave en la cabecera X-Bifrost-Invoke en cada solicitud. No hay paso de inicio de sesión ni token que renovar: la clave es la credencial.

bash
curl -X GET "https://bridge.bifrostcrypto.com/v1/balances" \
  -H "X-Bifrost-Invoke: YOUR_API_KEY"

El acceso se vuelve a verificar en cada solicitud. Revocar la clave, dejarla expirar o tener el acceso a la API desactivado en la cuenta surte efecto en la siguiente solicitud, sin esperar a que venza ninguna caché.

  • Encabezado ausente: 401 api_key_missing. Clave desconocida, revocada o expirada: 401 api_key_invalid.
  • Acceso a la API desactivado en la cuenta: 403 api_access_denied.
  • E-mail de la cuenta no verificado: 403 email_not_verified. Verifique el e-mail en el panel; la clave empieza a funcionar en la siguiente solicitud.
  • Cuenta suspendida: las claves se conservan, pero responden 401 api_key_invalid mientras dure la suspensión y vuelven a funcionar cuando se reactiva la cuenta.

Alcances

Un alcance es el permiso de llegar a un grupo de endpoints. Al crear una clave eliges qué alcances lleva, de modo que una clave usada solo para leer saldos no pueda crear un retiro aunque se filtre.

Una clave creada sin lista de alcances es de solo lectura: payout:read, payin:read, balances:read, statements:read y prices:read. No tiene alcance de escritura ni de direcciones fijas.

AlcanceDa acceso a
payout:readListar retiros y consultar el estado de un retiro
payout:writeCrear retiros
payin:readListar depósitos y consultar el estado de un depósito
payin:writeCrear depósitos
balances:readLeer los saldos de la cuenta
statements:readLeer extractos, resúmenes, tokens aceptados y monedas fiduciarias aceptadas
prices:readLeer cotizaciones y el catálogo de monedas
wallets:readListar carteras fijas y leer su configuración
wallets:writeCrear carteras fijas y modificar su configuración

Llamar a un endpoint fuera de los ámbitos de la clave devuelve 403 indicando qué ámbitos servirían, para que la corrección sea visible sin adivinar. Algunos endpoints aceptan más de uno, y basta con tener cualquiera:

json
{
  "error": {
    "code": "scope_denied",
    "message": "This API key does not have permission for this endpoint",
    "details": {
      "required_scopes": ["payout:write"],
      "granted_scopes": ["balances:read", "payin:read"]
    }
  }
}

En las claves con alcance de escritura (payout:write, payin:write, wallets:write) conviene sumar una segunda capa al alcance: lista blanca de IP, si su integración llama desde una dirección fija, o firma de la solicitud, si no lo hace. Ninguna de las dos es obligatoria, pero ambas existen porque una credencial que mueve dinero merece más que un secreto estático.

Lista blanca de IP

La lista blanca es opcional, en cualquier alcance. Sin ella la clave se acepta desde cualquier origen, y nada de lo que ya ejecuta necesita cambiar. Es una capa adicional para quien llama desde una dirección fija; si su integración no tiene IP fija, use require_signature, que protege una clave filtrada sin depender del origen.

La clave puede restringirse a un conjunto de direcciones de origen. Se aceptan direcciones individuales y rangos en notación CIDR, tanto IPv4 como IPv6:

json
["203.0.113.10", "198.51.100.0/24", "2001:db8::/32"]

Una solicitud llega por una familia de direcciones a la vez. Si su infraestructura nos alcanza por IPv4 y por IPv6, declare ambas, porque una entrada de una familia no autoriza la otra. Las solicitudes de fuera de la lista se rechazan con 403 ip_not_authorized.

Firma de la Solicitud (HMAC)

La firma es opcional y se activa por clave, al momento de crearla. Las claves que no la exigen siguen autenticando solo con X-Bifrost-Invoke, sin ningún encabezado adicional, y nada de lo que ya ejecuta se ve afectado.

Opcional por clave. La clave de API por sí sola prueba que quien llamó conoce un secreto estático, pero no prueba que la solicitud sea reciente, que el cuerpo esté íntegro, ni que esa llamada no haya ocurrido ya. Con la firma activada, cada solicitud lleva una prueba válida una sola vez y por una ventana corta.

CabeceraDescripción
X-Bifrost-TimestampHora Unix en segundos enteros, solo dígitos, en el momento en que se construyó la solicitud. Se rechaza si difiere de la hora del servidor en más de 300 segundos.
X-Bifrost-NonceValor único por solicitud, de 16 a 80 caracteres entre A-Z, a-z, 0-9, guion bajo y guion. Se acepta una vez por clave.
X-Bifrost-SignatureHMAC-SHA256 de la cadena canónica, con su secreto de firma como clave, en hexadecimal. Envíelo en minúsculas; las mayúsculas también se aceptan.

La cadena canónica

Construye estos seis campos unidos por un único carácter de salto de línea, exactamente en este orden:

text
METHOD
PATH
QUERY
TIMESTAMP
NONCE
SHA256_HEX(BODY)

METHOD en mayúsculas. PATH es la ruta exactamente como aparece en la línea de la solicitud, con la barra inicial y el prefijo /v1 (por ejemplo /v1/check-payout/f47ac10b-58cc-4372-a567-0e02b2c3d479): no se decodifica y la barra final se conserva. QUERY es la query string cruda tal como se envía, sin el signo de interrogación inicial, sin reordenar ni recodificar, y queda vacía cuando no hay parámetros. El último campo es el SHA-256 en hexadecimal de los bytes exactos del cuerpo que envía. En solicitudes sin cuerpo, use el hash de la cadena vacía, que es e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

Firmando una solicitud

javascript
const crypto = require("crypto");

const secret = process.env.BIFROST_SIGNING_SECRET;
const method = "POST";
const path = "/v1/payout";
const query = "";
const body = JSON.stringify({ /* ... */ });

const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = crypto.createHash("sha256").update(body).digest("hex");

const canonical = [method, path, query, timestamp, nonce, bodyHash].join("\n");
const signature = crypto
  .createHmac("sha256", secret)
  .update(canonical)
  .digest("hex");

await fetch("https://bridge.bifrostcrypto.com" + path, {
  method,
  headers: {
    "X-Bifrost-Invoke": process.env.BIFROST_API_KEY,
    "X-Bifrost-Timestamp": timestamp,
    "X-Bifrost-Nonce": nonce,
    "X-Bifrost-Signature": signature,
    "Content-Type": "application/json",
  },
  body,
});

Puntos a tener en cuenta

  • El secreto de firma se muestra una sola vez, al crear la clave. Nunca se devuelve de nuevo, así que guárdalo antes de cerrar el panel.
  • El secreto es distinto de la clave de API. Ambos son necesarios: la clave te identifica, la firma prueba la solicitud.
  • Calcula el hash de los mismos bytes que transmites. Serializar el cuerpo dos veces, o volver a serializarlo tras firmar, cambia el resumen e invalida la firma.
  • Cada nonce se acepta una vez por clave. Reenviar la solicitud idéntica devuelve signature_replayed aunque todo lo demás sea correcto.
  • El desfase de reloj es la causa más común de signature_timestamp_out_of_range. Mantén tus servidores con NTP.

Límites de Solicitudes

Cada clave tiene su propia cuota en una ventana fija alineada al reloj: por defecto 500 solicitudes cada 60 segundos, definidas al crear la clave. Las respuestas traen el estado actual, para que regule su consumo en vez de descubrir el límite al chocar con él:

http
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1760054460
  • X-RateLimit-Limit es la cuota, X-RateLimit-Remaining lo que queda en la ventana y X-RateLimit-Reset la hora Unix en que la ventana se reinicia.
  • Los encabezados llegan en toda respuesta que pasó la autenticación y la verificación de scope, incluidas respuestas 4xx y 5xx del endpoint.
  • No aparecen en los rechazos de autenticación y de scope, ni en claves configuradas sin cuota, que no tienen límite de solicitudes.

Superada la cuota, las solicitudes devuelven 429 rate_limit_exceeded con el encabezado Retry-After hasta que la ventana se reinicie. No hay penalización adicional:

http
HTTP/1.1 429 Too Many Requests
Retry-After: 24
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1760054460

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "You have exceeded the allowed number of requests",
    "details": {
      "limit": 500,
      "window_seconds": 60,
      "retry_after_seconds": 24,
      "retry_after_formatted": "24 seconds"
    }
  }
}

Fallos repetidos de autenticación

Independientemente de la cuota por clave, una dirección IP que falla la autenticación repetidamente queda bloqueada durante un tiempo. Mientras está bloqueada recibe 429 too_many_failed_authentications con el encabezado Retry-After: espere esos segundos antes de reintentar. Las solicitudes exitosas nunca cuentan para este bloqueo.

json
{
  "error": {
    "code": "too_many_failed_authentications",
    "message": "Too many failed authentication attempts from this address",
    "details": {
      "retry_after_seconds": 900,
      "retry_after_formatted": "15 minutes"
    }
  }
}