Autenticação e Controle de Acesso

Toda requisição /v1 é autenticada por chave de API. A chave pode ainda ser restringida por escopo, por IP de origem e por assinatura da requisição.

ᚱ
"Heimdall sabe o nome, a hora e o propósito de cada viajante que pisa na ponte. Quem não souber dizer os três não atravessa."

Chave de API

Envie sua chave no cabeçalho X-Bifrost-Invoke em toda requisição. Não há etapa de login nem token a renovar: a chave é a credencial.

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

O acesso é reconferido a cada requisição. Revogar a chave, deixá-la expirar ou ter o acesso à API desativado na conta vale já na requisição seguinte, sem esperar cache nenhum vencer.

  • Cabeçalho ausente: 401 api_key_missing. Chave desconhecida, revogada ou expirada: 401 api_key_invalid.
  • Acesso à API desativado na conta: 403 api_access_denied.
  • E-mail da conta não verificado: 403 email_not_verified. Verifique o e-mail no painel; a chave passa a funcionar na requisição seguinte.
  • Conta suspensa: as chaves são mantidas, mas respondem 401 api_key_invalid enquanto durar a suspensão, e voltam a funcionar quando a conta é reativada.

Escopos

Escopo é a permissão de alcançar um grupo de endpoints. Ao criar uma chave você escolhe quais escopos ela carrega, de modo que uma chave usada só para ler saldos não consiga criar um saque nem se vazar.

Uma chave criada sem lista de escopos é somente leitura: payout:read, payin:read, balances:read, statements:read e prices:read. Ela não tem escopo de escrita nem de endereços fixos.

EscopoDá acesso a
payout:readListar saques e consultar o status de um saque
payout:writeCriar saques
payin:readListar depósitos e consultar o status de um depósito
payin:writeCriar depósitos
balances:readLer os saldos da conta
statements:readLer extratos, resumos, tokens aceitos e moedas fiduciárias aceitas
prices:readLer cotações e o catálogo de moedas
wallets:readListar carteiras fixas e ler a configuração delas
wallets:writeCriar carteiras fixas e alterar a configuração delas

Chamar um endpoint fora dos escopos da chave devolve 403 informando quais escopos serviriam, para que a correção fique visível sem adivinhação. Alguns endpoints aceitam mais de um, e ter qualquer um deles basta:

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"]
    }
  }
}

Nas chaves com escopo de escrita (payout:write, payin:write, wallets:write) vale somar uma segunda camada ao escopo: whitelist de IP, se a sua integração chama de endereço fixo, ou assinatura da requisição, se não chama. Nenhuma das duas é obrigatória, e as duas existem porque uma credencial que movimenta dinheiro merece mais que um segredo estático.

Whitelist de IP

A whitelist é opcional, em qualquer escopo. Sem ela, a chave é aceita a partir de qualquer origem, e nada do que você já roda precisa mudar. Ela é uma camada a mais para quem chama de endereço fixo; se a sua integração não tem IP fixo, use require_signature, que protege a chave vazada sem depender da origem.

A chave pode ser restringida a um conjunto de endereços de origem. São aceitos endereços individuais e faixas em notação CIDR, tanto IPv4 quanto IPv6:

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

Uma requisição chega por uma família de endereços de cada vez. Se sua infraestrutura nos alcança por IPv4 e por IPv6, declare as duas, porque uma entrada de uma família não autoriza a outra. Requisições de fora da lista são recusadas com 403 ip_not_authorized.

Assinatura da Requisição (HMAC)

A assinatura é opcional e se liga por chave, no momento da criação. Chaves que não a exigem continuam autenticando apenas com o X-Bifrost-Invoke, sem nenhum cabeçalho a mais, e nada do que você já roda é afetado.

Opcional por chave. A chave de API sozinha prova que quem chamou conhece um segredo estático, mas não prova que a requisição é recente, que o corpo está íntegro, nem que aquela chamada já não aconteceu. Com assinatura ligada, cada requisição carrega uma prova válida uma única vez e por uma janela curta.

CabeçalhoDescrição
X-Bifrost-TimestampHorário Unix em segundos inteiros, só dígitos, no momento em que a requisição foi montada. Recusado se diferir do horário do servidor em mais de 300 segundos.
X-Bifrost-NonceValor único por requisição, de 16 a 80 caracteres entre A-Z, a-z, 0-9, sublinhado e hífen. Aceito uma vez por chave.
X-Bifrost-SignatureHMAC-SHA256 da string canônica, com o seu segredo de assinatura como chave, em hexadecimal. Envie em minúsculas; maiúsculas também são aceitas.

A string canônica

Monte estes seis campos unidos por um único caractere de quebra de linha, exatamente nesta ordem:

text
METHOD
PATH
QUERY
TIMESTAMP
NONCE
SHA256_HEX(BODY)

METHOD em maiúsculas. PATH é o caminho exatamente como aparece na linha da requisição, com a barra inicial e o prefixo /v1 (por exemplo /v1/check-payout/f47ac10b-58cc-4372-a567-0e02b2c3d479): não é decodificado e a barra final é mantida. QUERY é a query string crua como enviada, sem o ponto de interrogação inicial, sem reordenar e sem recodificar, e fica vazia quando não há parâmetros. O último campo é o SHA-256 em hexadecimal dos bytes exatos do corpo que você envia. Em requisições sem corpo, use o hash da string vazia, que é e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

Assinando uma requisição

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,
});

Pontos de atenção

  • O segredo de assinatura é exibido uma única vez, na criação da chave. Ele nunca mais é devolvido, então guarde antes de fechar o painel.
  • O segredo é separado da chave de API. Os dois são necessários: a chave identifica você, a assinatura prova a requisição.
  • Faça o hash dos mesmos bytes que você transmite. Serializar o corpo duas vezes, ou reserializar depois de assinar, muda o resumo e invalida a assinatura.
  • Cada nonce é aceito uma vez por chave. Reenviar a requisição idêntica devolve signature_replayed mesmo com todo o resto correto.
  • Relógio fora de hora é a causa mais comum de signature_timestamp_out_of_range. Mantenha seus servidores em NTP.

Limites de Requisição

Cada chave tem cota própria em uma janela fixa alinhada ao relógio: por padrão 500 requisições a cada 60 segundos, definidas na criação da chave. As respostas trazem o estado atual, para você regular o consumo em vez de descobrir o limite ao esbarrar nele:

http
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1760054460
  • X-RateLimit-Limit é a cota, X-RateLimit-Remaining o que resta na janela e X-RateLimit-Reset o horário Unix em que a janela recomeça.
  • Os cabeçalhos vêm em toda resposta que passou pela autenticação e pela conferência de escopo, inclusive respostas 4xx e 5xx do endpoint.
  • Eles não aparecem nas recusas de autenticação e de escopo, nem em chaves configuradas sem cota, que não têm limite de requisições.

Passando da cota, as requisições devolvem 429 rate_limit_exceeded com o cabeçalho Retry-After até a janela recomeçar. Não há punição 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"
    }
  }
}

Falhas repetidas de autenticação

Independentemente da cota por chave, um endereço IP que erra a autenticação repetidamente fica bloqueado por um tempo. Enquanto bloqueado, recebe 429 too_many_failed_authentications com o cabeçalho Retry-After: aguarde esses segundos antes de tentar de novo. Requisições bem-sucedidas nunca contam para esse bloqueio.

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"
    }
  }
}