Codes d'Erreur
Tous les codes d'erreur renvoyés par l'API, regroupés par type, avec la conduite à tenir pour chacun.
ᚱ
"Lorsque les forces du chaos interfèrent, les dieux envoient des messages clairs aux mortels. Apprenez les codes sacrés qui révèlent la nature de chaque obstacle sur le chemin."Les Gardiens de Bifrost
Format de l'erreur
Toute erreur /v1 a la même forme, avec le statut HTTP des tableaux ci-dessous. Comparez error.code, qui est un snake_case stable. error.message est un texte libre en anglais pour vos logs. error.details n'apparaît que s'il y a des données utiles, comme des champs invalides, des limites ou des scopes.
json
{
"error": {
"code": "insufficient_total_balance",
"message": "Insufficient balance to cover all payouts",
"details": {
"crypto_currency": "USDT",
"available": "150.00000000",
"required": "420.50000000"
}
}
}Dans un lot POST /v1/payout, chaque élément refusé porte le même objet dans results[].error, à côté de son index et de son reference_id. La réponse est 200 quand tous les éléments ont réussi, 207 quand certains ont réussi et d'autres échoué, et 400 quand aucun n'a réussi :
json
{
"message": "payment_processing_completed",
"summary": {
"total_requested": 3,
"successful": 1,
"failed": 2
},
"results": [
{
"index": 0,
"reference_id": "order_1001",
"internal_reference": "BIFROST_REF_123456",
"status": "created"
},
{
"index": 1,
"reference_id": "order_1002",
"error": {
"code": "invalid_parameters",
"message": "Invalid parameters provided",
"details": {
"fields": {
"wallet": "Invalid wallet format"
}
}
}
},
{
"index": 2,
"reference_id": "order_1003",
"error": {
"code": "daily_payout_limit_exceeded",
"message": "Daily payout limit for this API key exceeded"
}
}
]
}- Les messages de validation se trouvent toujours dans error.details.fields, indexés par nom de champ, au premier niveau comme dans un élément du lot.
- Un élément de payout en double compte comme un échec : il porte error.code duplicate_payment_reference ainsi que duplicate, original_status et original_created_at dans l'élément. Un lot composé uniquement de doublons répond 400.
- Décidez selon error.code, jamais selon error.message : les messages peuvent être reformulés, les codes ne changent pas.
- 4xx signifie que la requête doit être modifiée avant d'être renvoyée. 5xx signifie que l'échec vient de notre côté et que la même requête peut être réessayée ; sur POST /v1/payout, le reference_id vous protège toujours d'un double retrait.
- Un chemin /v1 inconnu renvoie 404 route_not_found en JSON. Un client qui demande text/html dans Accept reçoit une page HTML.
Erreurs d'Authentification et d'Accès
| Code HTTP | Erreur | Description |
|---|---|---|
| 401 | api_key_missing | L'en-tête X-Bifrost-Invoke n'a pas été envoyé. |
| 401 | api_key_invalid | Clé API inconnue, révoquée ou expirée. Également renvoyé tant que le compte est suspendu. |
| 403 | api_access_denied | L'accès à l'API est désactivé pour ce compte. Vérifié à chaque requête. |
| 403 | email_not_verified | L'adresse e-mail du compte n'a pas encore été vérifiée. |
| 403 | ip_not_authorized | L'IP source n'est pas dans la whitelist de la clé. |
| 400 | invalid_ip | Impossible de déterminer l'adresse IP du client. |
| 401 | signature_headers_missing | La clé exige une signature et X-Bifrost-Signature, X-Bifrost-Timestamp ou X-Bifrost-Nonce est absent. |
| 401 | signature_timestamp_invalid | X-Bifrost-Timestamp ne contient pas uniquement des chiffres (secondes Unix). |
| 401 | signature_timestamp_out_of_range | Le timestamp s'écarte de l'heure du serveur de plus de 300 secondes. |
| 401 | signature_nonce_length_invalid | Le nonce fait moins de 16 ou plus de 80 caractères. |
| 401 | signature_nonce_charset_invalid | Le nonce contient des caractères hors de A-Z, a-z, 0-9, tiret bas et tiret. |
| 401 | signature_invalid | La signature ne correspond pas à la chaîne canonique. |
| 401 | signature_replayed | Ce nonce a déjà été utilisé par cette clé. |
| 401 | signature_secret_unavailable | La clé exige une signature mais n'a pas de secret utilisable. Contactez le support. |
| 403 | scope_denied | La clé n'a pas le scope exigé par cet endpoint. details.required_scopes liste ceux qui conviennent et details.granted_scopes ceux de la clé. |
| 429 | rate_limit_exceeded | Le quota de la clé pour la fenêtre en cours est épuisé. Retry-After et details.retry_after_seconds indiquent quand réessayer. |
| 429 | too_many_failed_authentications | Cette IP est temporairement bloquée après des échecs d'authentification répétés. Envoyé avec Retry-After. |
| 401 | user_not_authenticated | La requête n'a pas pu être rattachée à votre clé d'API. Réessayez ; si cela se reproduit, contactez le support. |
Erreurs de Requête
| Code HTTP | Erreur | Description |
|---|---|---|
| 404 | route_not_found | Aucune route /v1 ne correspond au chemin et à la méthode. Répondu avant l'authentification. |
| 400 | json_parse_exception | Le corps de la requête n'est pas un JSON valide. Toute route /v1. |
| 400 | json_parse_failed | POST /v1/payout : le corps a été décodé en null ou false. |
| 400 | invalid_parameters | Paramètres invalides ou manquants. details.fields associe chaque champ à son message (POST /v1/payout avec tous les éléments invalides, POST /v1/payin, adresses fixes, filtres de relevés). |
| 400 | invalid_payment_list | POST /v1/payout : le corps est vide ou n'est ni un objet payout ni une liste de payouts. |
| 400 | max_payouts_exceeded | POST /v1/payout : plus de 50 payouts dans une requête. |
| 409 | idempotency_key_in_progress | Une requête avec cette Idempotency-Key est encore en cours. Attendez et réessayez avec la même clé. |
| 400 | crypto_amount_required | POST /v1/payin : process_by vaut crypto_amount et crypto_amount est absent. |
| 400 | fiat_amount_required | POST /v1/payin : process_by vaut fiat_amount et fiat_amount est absent. |
| 400 | fiat_currency_required | POST /v1/payin : process_by vaut fiat_amount et fiat_currency est absent. |
| 400 | invalid_page_number | list-payout / list-payin : page inférieur à 1. |
| 400 | invalid_per_page_limit | list-payout / list-payin : per_page hors de 1 à 100. |
| 400 | invalid_status_filter | list-payout / list-payin : valeur de status inconnue. |
| 400 | invalid_network_filter | list-payout / list-payin : réseau inconnu. |
| 400 | invalid_date_from | list-payout / list-payin : date_from n'est pas une date. |
| 400 | invalid_date_to | list-payout / list-payin : date_to n'est pas une date. |
| 400 | invalid_reference_id | check-payout / check-payin : référence de moins de 10 ou plus de 756 caractères. Relevés : reference_id de plus de 255. |
| 400 | invalid_pagination | Relevés : page ou per_page hors limites. |
| 400 | invalid_format | Relevés : date_from ou date_to n'est pas une date (details.field). |
| 400 | invalid_date_range | Relevés : date_from est postérieur à date_to. |
| 400 | date_range_too_wide | Relevés : la plage dépasse le maximum autorisé. details.max_days indique le plafond. |
| 400 | invalid_amount | Relevés : amount_from ou amount_to invalide, ou amount_from supérieur à amount_to. |
| 400 | missing_parameters | get-price : currency1 ou currency2 est absent. |
| 400 | invalid_currency | get-price : currency1 ou currency2 n'est pas un code de devise valide. |
| 400 | invalid_webhook_url | PUT /v1/fixed-wallets/config : webhook_url pointe vers une destination non autorisée. |
Erreurs Métier
| Code HTTP | Erreur | Description |
|---|---|---|
| 400 | insufficient_total_balance | POST /v1/payout : les éléments d'une devise dépassent le solde disponible. Rien n'est débité. details contient crypto_currency, available et required. |
| 404 | user_not_found | POST /v1/payout : la configuration du compte est incomplète. Contactez le support. |
| 404 | record_not_found | Le payout ou payin recherché par check-payout / check-payin n'existe pas sur ce compte. Aussi renvoyé quand une donnée du compte nécessaire à la requête manque. |
| 400 | duplicate_reference_id | POST /v1/payin : un payin avec ce reference_id existe déjà sur le compte. |
| 400 | invalid_network_or_deposits_disabled | POST /v1/payin : réseau inconnu, ou dépôts désactivés sur celui-ci. |
| 400 | unsupported_token_or_deposits_disabled | POST /v1/payin : token non pris en charge, ou dépôts désactivés pour celui-ci. |
| 400 | token_network_mismatch | POST /v1/payin : le token n'est pas disponible sur ce réseau. |
| 400 | memo_required | POST /v1/payin : ce token exige un memo. |
| 400 | amount_out_of_limits | POST /v1/payin : le montant crypto est hors des limites du token. details contient min_allowed et max_allowed. |
| 400 | value_below_minimum | POST /v1/payin avec fiat_amount : le montant converti est inférieur au minimum. |
| 400 | value_above_maximum | POST /v1/payin avec fiat_amount : le montant converti est supérieur au maximum. |
| 400 | token_not_supported_or_inactive | POST /v1/payin avec fiat_amount : le token n'est pas pris en charge ou est inactif pour la conversion. |
| 400 | crypto_amount_does_not_correspond_to_fiat_amount_provided | POST /v1/payin : crypto_amount et fiat_amount ont été envoyés ensemble et ne correspondent pas au cours actuel. |
| 400 | invalid_parameters_for_process_by | POST /v1/payin : les montants envoyés ne correspondent pas au process_by choisi. |
| 404 | price_not_found | get-price : aucun cours pour cette paire de devises. |
| 403 | feature_disabled | Les adresses fixes ne sont pas activées pour le compte (config, mise à jour de la config et création). |
| 422 | type_not_supported | POST /v1/fixed-wallets : ce type n'est pas disponible pour les adresses fixes. |
| 403 | limit_not_allowed | POST /v1/fixed-wallets : la limite du compte pour ce type est zéro. |
| 409 | limit_reached | POST /v1/fixed-wallets : le compte détient déjà le nombre maximal d'adresses fixes. |
| 409 | no_wallet_available | POST /v1/fixed-wallets : aucune adresse de ce type n'est disponible pour le moment. Réessayez plus tard. |
Erreurs par Élément du Lot de Payout (results[].error)
| Code HTTP | Erreur | Description |
|---|---|---|
| 207/400 | invalid_parameters | L'élément a échoué à la validation. error.details.fields associe chaque champ à son message. |
| 207/400 | missing_required_fields | Un champ obligatoire de l'élément est absent. |
| 207/400 | duplicate_reference_id_in_request | Le même reference_id apparaît plus haut dans le même lot. |
| 207/400 | duplicate_payment_reference | Le reference_id a déjà été utilisé sur ce compte. L'élément contient duplicate, original_status, original_created_at et l'internal_reference d'origine ; aucun second transfert n'est effectué. |
| 207/400 | invalid_crypto_amount | process_by vaut crypto_amount et crypto_amount est absent ou invalide. |
| 207/400 | invalid_fiat_amount | process_by vaut fiat_amount et fiat_amount est absent ou invalide. |
| 207/400 | fiat_currency_required | process_by vaut fiat_amount et fiat_currency est absent. |
| 207/400 | fiat_currency_not_accepted | La fiat_currency n'est pas acceptée. |
| 207/400 | memo_required | Ce token exige un memo. |
| 207/400 | invalid_network_or_withdrawals_disabled | Réseau inconnu, ou retraits désactivés sur celui-ci. |
| 207/400 | unsupported_token_or_withdrawals_disabled | Token non pris en charge, ou retraits désactivés pour celui-ci. |
| 207/400 | token_network_mismatch | Le token n'est pas disponible sur ce réseau. |
| 207/400 | token_not_active | Le token n'est pas actif pour les retraits ou pour la conversion. |
| 207/400 | invalid_format | L'adresse du wallet n'est pas valide pour le réseau. |
| 207/400 | limit_exceeded | Le montant est hors du minimum et du maximum du token (aussi quand le montant fiat converti en sort). |
| 207/400 | amount_does_not_cover_fees | Les frais absorbent tout le montant, le destinataire ne recevrait rien. |
| 207/400 | crypto_amount_does_not_correspond_to_fiat_amount_provided | crypto_amount et fiat_amount ne correspondent pas au cours actuel. |
| 207/400 | price_unavailable | Pas de cours pour convertir le montant, ou la clé a un plafond journalier et le token n'a pas de cours en USD pour compter le retrait. |
| 207/400 | insufficient_balance | Le solde disponible ne couvre pas cet élément. |
| 207/400 | daily_payout_limit_exceeded | L'élément dépasserait le plafond journalier configuré sur la clé. |
| 207/400 | daily_payout_limit_unavailable | La clé a un plafond journalier mais la dépense n'a pas pu être enregistrée. Renvoyez l'élément. |
| 207/400 | error_fetching_active_tokens · error_fetching_active_currencies | Le catalogue de tokens ou de devises n'a pas pu être chargé. Renvoyez l'élément. |
| 207/400 | user_not_found · record_not_found | Une donnée du compte nécessaire au retrait manque. |
| 207/400 | internal_error · database_error · transaction_failed · network_unavailable · duplicate_record | L'enregistrement du retrait a échoué de notre côté et a été annulé. Rien n'a été débité pour cet élément ; vérifiez avec check-payout ou list-payout avant de renvoyer. |
Erreurs Système
| Code HTTP | Erreur | Description |
|---|---|---|
| 500 | internal_error | Échec inattendu. Sur POST /v1/payout, un échec en cours de lot renvoie aussi summary (avec not_processed) et results au premier niveau, pour ne renvoyer que ce qui manque. |
| 500 | database_error | Relevés : la requête a échoué. |
| 500 | error_fetching_active_currencies | POST /v1/payout et POST /v1/payin : le catalogue de tokens ou de fiats n'a pas pu être chargé. Rien n'a été traité. |
| 500 | token_type_not_found · receipt_creation_failed · database_transaction_failed | POST /v1/payin : le payin n'a pas pu être créé de notre côté. |
| 502 | external_processor_error | POST /v1/payin et POST /v1/fixed-wallets : l'adresse de dépôt n'a pas pu être créée pour le moment. Rien n'a été créé ; réessayez plus tard. |
| 503 | no_processor_configured · no_wallet_available | POST /v1/payin : aucun processeur ni adresse de dépôt disponible pour ce token et ce réseau pour le moment. Réessayez plus tard. |
| 503 | price_not_available_for_conversion · price_not_available_for_validation | POST /v1/payin : aucun cours disponible pour convertir ou vérifier le montant. Réessayez plus tard. |
| 503 | service_unavailable | POST /v1/payin et endpoints summary : une dépendance est temporairement indisponible. |
| 500 | config_update_failed | PUT /v1/fixed-wallets/config : la configuration n'a pas pu être enregistrée. |
| 500 | assign_failed | POST /v1/fixed-wallets : l'adresse n'a pas pu être attribuée. |
Meilleures Pratiques
ᚱ
"La sagesse des anciens enseigne que suivre les bons chemins garantit un voyage sûr et prospère à travers les neuf royaumes numériques."Mímir, Gardien de la Sagesse Ancestrale
Sécurité
À Faire
- •Stockez votre clé API dans des variables d'environnement
- •Configurez la whitelist IP
- •Vérifiez le X-Bifrost-Signature de chaque webhook
- •Utilisez HTTPS dans toutes les requêtes
- •Implémentez des timeouts pour les requêtes
À Ne Pas Faire
- •Partager la clé API publiquement
- •Commiter les clés API dans Git
- •Utiliser la même clé API dans plusieurs environnements
Performance
- •Les données de relevé et de résumé peuvent avoir jusqu'à 5 minutes de retard ; ne les interrogez pas plus souvent
- •Implémentez une pagination appropriée
- •Évitez les requêtes excessives (respectez la limite de taux)
- •Traitez les webhooks de manière asynchrone
Idempotence
- •Utilisez un reference_id unique et significatif : c'est le verrou contre le double paiement, et le représenter renvoie le paiement d'origine au lieu d'en créer un autre
- •Envoyez une Idempotency-Key sur les lots : si la connexion tombe, réessayer avec la même clé renvoie la réponse d'origine au lieu de retraiter
- •POST /v1/payin n'a pas d'Idempotency-Key : après un timeout, renvoyez avec le même reference_id et, si la réponse est duplicate_reference_id, récupérez le payin avec GET /v1/list-payin?reference_id=…
- •Traitez les 207 et 500 en lisant results : les deux contiennent ce qui a déjà été créé, pour ne renvoyer que ce qui manque
- •Gérez les renvois de webhooks en dédupliquant sur data.internal_reference avec event, pour ne pas traiter deux fois le même avis