Authentification et Contrôle d'Accès
Chaque requête /v1 est authentifiée par clé d'API. La clé peut en outre être restreinte par portée, par IP source et par signature de la requête.
"Heimdall connaît le nom, l'heure et le dessein de chaque voyageur qui pose le pied sur le pont. Qui ne sait dire les trois ne traverse pas."
Clé d'API
Envoyez votre clé dans l'en-tête X-Bifrost-Invoke à chaque requête. Il n'y a ni étape de connexion ni jeton à renouveler : la clé est la crédence.
curl -X GET "https://bridge.bifrostcrypto.com/v1/balances" \
-H "X-Bifrost-Invoke: YOUR_API_KEY"L'accès est revérifié à chaque requête. Révoquer la clé, la laisser expirer ou voir l'accès API désactivé sur le compte prend effet dès la requête suivante, sans attendre l'expiration d'un cache.
- En-tête absent : 401 api_key_missing. Clé inconnue, révoquée ou expirée : 401 api_key_invalid.
- Accès API désactivé pour le compte : 403 api_access_denied.
- E-mail du compte non vérifié : 403 email_not_verified. Vérifiez l'e-mail dans le panneau ; la clé fonctionne dès la requête suivante.
- Compte suspendu : les clés sont conservées mais répondent 401 api_key_invalid pendant la suspension, et fonctionnent de nouveau à la réactivation du compte.
Portées
Une portée est l'autorisation d'atteindre un groupe de points de terminaison. À la création d'une clé, vous choisissez les portées qu'elle porte : une clé servant uniquement à lire les soldes ne peut pas créer un retrait, même si elle fuite.
Une clé créée sans liste de scopes est en lecture seule : payout:read, payin:read, balances:read, statements:read et prices:read. Elle n'a aucun scope d'écriture ni d'adresses fixes.
| Portée | Donne accès à |
|---|---|
payout:read | Lister les retraits et consulter le statut d'un retrait |
payout:write | Créer des retraits |
payin:read | Lister les dépôts et consulter le statut d'un dépôt |
payin:write | Créer des dépôts |
balances:read | Lire les soldes du compte |
statements:read | Lire les relevés, les résumés, les jetons acceptés et les monnaies fiduciaires acceptées |
prices:read | Lire les cotations et le catalogue des devises |
wallets:read | Lister les portefeuilles fixes et lire leur configuration |
wallets:write | Créer des portefeuilles fixes et modifier leur configuration |
Appeler un endpoint hors des portées de la clé renvoie 403 en indiquant quelles portées conviendraient, pour que la correction soit visible sans deviner. Certains endpoints en acceptent plusieurs, et en détenir une seule suffit :
{
"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"]
}
}
}Sur les clés portant une portée d'écriture (payout:write, payin:write, wallets:write), il vaut la peine d'ajouter une seconde couche à la portée : une liste blanche d'IP si votre intégration appelle depuis une adresse fixe, ou la signature de la requête sinon. Aucune des deux n'est obligatoire, mais elles existent parce qu'une crédence qui déplace de l'argent mérite mieux qu'un secret statique.
Liste blanche d'IP
La liste blanche est facultative, quelle que soit la portée. Sans elle, la clé est acceptée depuis n'importe quelle origine, et rien de ce que vous exécutez déjà n'a besoin de changer. C'est une couche supplémentaire pour ceux qui appellent depuis une adresse fixe ; si votre intégration n'a pas d'IP fixe, utilisez require_signature, qui protège une clé divulguée sans dépendre de l'origine.
La clé peut être restreinte à un ensemble d'adresses sources. Les adresses individuelles et les plages en notation CIDR sont acceptées, en IPv4 comme en IPv6 :
["203.0.113.10", "198.51.100.0/24", "2001:db8::/32"]Une requête arrive par une seule famille d'adresses à la fois. Si votre infrastructure nous atteint en IPv4 et en IPv6, déclarez les deux, car une entrée d'une famille n'autorise pas l'autre. Les requêtes hors de la liste sont refusées avec 403 ip_not_authorized.
Signature de la Requête (HMAC)
La signature est facultative et s'active par clé, au moment de la création. Les clés qui ne l'exigent pas continuent de s'authentifier avec le seul X-Bifrost-Invoke, sans en-tête supplémentaire, et rien de ce que vous exécutez déjà n'est affecté.
Facultative par clé. La clé d'API seule prouve que l'appelant connaît un secret statique, mais elle ne prouve ni que la requête est récente, ni que le corps est intact, ni que cet appel n'a pas déjà eu lieu. Avec la signature activée, chaque requête porte une preuve valable une seule fois et sur une courte fenêtre.
| En-tête | Description |
|---|---|
X-Bifrost-Timestamp | Heure Unix en secondes entières, chiffres uniquement, au moment où la requête a été construite. Refusée si elle s'écarte de l'heure du serveur de plus de 300 secondes. |
X-Bifrost-Nonce | Valeur unique par requête, de 16 à 80 caractères parmi A-Z, a-z, 0-9, tiret bas et trait d'union. Acceptée une fois par clé. |
X-Bifrost-Signature | HMAC-SHA256 de la chaîne canonique, avec votre secret de signature comme clé, en hexadécimal. Envoyez-le en minuscules ; les majuscules sont aussi acceptées. |
La chaîne canonique
Construisez ces six champs joints par un unique caractère de saut de ligne, exactement dans cet ordre :
METHOD
PATH
QUERY
TIMESTAMP
NONCE
SHA256_HEX(BODY)METHOD en majuscules. PATH est le chemin exactement tel qu'il figure dans la ligne de requête, avec la barre initiale et le préfixe /v1 (par exemple /v1/check-payout/f47ac10b-58cc-4372-a567-0e02b2c3d479) : il n'est pas décodé et la barre finale est conservée. QUERY est la query string brute telle qu'envoyée, sans point d'interrogation initial, ni triée ni réencodée, et vide en l'absence de paramètres. Le dernier champ est le SHA-256 hexadécimal des octets exacts du corps envoyé. Pour les requêtes sans corps, hachez la chaîne vide, ce qui donne e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
Signer une requête
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,
});Points de vigilance
- Le secret de signature est affiché une seule fois, à la création de la clé. Il n'est jamais renvoyé, conservez-le donc avant de fermer le panneau.
- Le secret est distinct de la clé d'API. Les deux sont nécessaires : la clé vous identifie, la signature prouve la requête.
- Hachez les octets que vous transmettez réellement. Sérialiser le corps deux fois, ou le re-sérialiser après signature, change le condensé et invalide la signature.
- Chaque nonce est accepté une fois par clé. Renvoyer la requête à l'identique renvoie signature_replayed même si tout le reste est correct.
- Le décalage d'horloge est la cause la plus fréquente de signature_timestamp_out_of_range. Gardez vos serveurs sur NTP.
Limites de Requêtes
Chaque clé a son propre quota sur une fenêtre fixe alignée sur l'horloge : par défaut 500 requêtes par 60 secondes, défini à la création de la clé. Les réponses portent l'état courant, pour que vous régliez votre consommation au lieu de découvrir la limite en la heurtant :
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1760054460- X-RateLimit-Limit est le quota, X-RateLimit-Remaining ce qui reste dans la fenêtre et X-RateLimit-Reset l'heure Unix de réinitialisation de la fenêtre.
- Les en-têtes accompagnent toute réponse ayant passé l'authentification et la vérification de scope, y compris les réponses 4xx et 5xx de l'endpoint.
- Ils sont absents des refus d'authentification et de scope, ainsi que pour les clés configurées sans quota, qui ne sont pas limitées.
Au-delà du quota, les requêtes renvoient 429 rate_limit_exceeded avec un en-tête Retry-After jusqu'à la réinitialisation de la fenêtre. Aucune pénalité supplémentaire :
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"
}
}
}Échecs répétés d'authentification
Indépendamment du quota par clé, une adresse IP qui échoue à s'authentifier de façon répétée est bloquée pendant un temps. Pendant le blocage, elle reçoit 429 too_many_failed_authentications avec l'en-tête Retry-After : attendez ce nombre de secondes avant de réessayer. Les requêtes réussies ne comptent jamais pour ce blocage.
{
"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"
}
}
}