# Codes d'Erreur

> Version Markdown de la documentation publique de l'API v1 de Bifrost Crypto, pour les agents d'IA et les générateurs de code. Même contenu que les pages HTML, généré à partir de la même source, sans navigation ni mise en forme.
>
> Version HTML: https://bifrostcrypto.com/fr/docs/error-codes
> Fichiers liés: [ai.md](https://bifrostcrypto.com/ai/fr.md) · [Postman](https://bifrostcrypto.com/postman-collection.json) · [OpenAPI](https://bifrostcrypto.com/openapi.json) · [llms.txt](https://bifrostcrypto.com/llms.txt)
> Autres langues: [Português](https://bifrostcrypto.com/ai/pt-BR/error-codes.md) · [English](https://bifrostcrypto.com/ai/en/error-codes.md) · [Español](https://bifrostcrypto.com/ai/es/error-codes.md)
> API v1 · dernière modification du contrat le 2026-10-10

Tous les codes d'erreur renvoyés par l'API, regroupés par type, avec la conduite à tenir pour chacun.

## 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

### 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
