Réponses et erreurs pour le code et les agents IA
Tout succès partage une même enveloppe, et toute erreur une même forme. Cette page décrit l’enveloppe, la forme de data route par route, la façon de réduire la réponse, comment lire un champ vide, et ce que veut dire chaque code d’erreur.
Un agent IA lit la même chose : le serveur MCP garde ces noms de champs, ces unités et ces conventions, et un appel d’outil en échec porte le même error.code stable.
#L’enveloppe standard
{
"status": "ok",
"server_time": "2026-10-10T08:51:22.927Z",
"data_type": "basis",
"symbol": "BTCUSDT",
"data": {
"time": "2026-10-10T08:51:14.104Z",
"basis_value": -42.0048049747,
"basis_pct": -0.0507185886791,
"futures_price": 82776.0067294,
"spot_price": 82818.0115344
}
}| Champ | Type | Description |
|---|---|---|
status | string | ok ou error. |
server_time | string | L’instant où la réponse a été produite, en ISO 8601 UTC à la milliseconde : le format de toutes les dates de data. Jamais l’heure de la donnée, que chaque ligne porte dans time. |
data_type | string | Le nom du type renvoyé. Il permet d’aiguiller une réponse sans dépendre du chemin appelé, utile quand un client passe par une file ou un cache. |
symbol | string | Le symbole de la requête, rappelé hors de data. Présent quand la route prend un symbole. |
timeframe | string | Le timeframe que vous avez envoyé (ou le défaut de la route), et seulement lui. ?fields= ne touche pas ces clés. |
step | string | Sur une liste (toute route /history et les séries d’événements) : l’écart entre ses lignes — le timeframe demandé, ou la cadence de la série quand la route n’en prend pas (1d pour une série journalière, 8h pour le funding, 1m pour les relevés de frais Bitcoin). Une ligne de bucket est datée au début de son bucket ; un relevé périodique à son propre instant. Absent quand les lignes n’ont pas d’écart fixe (dépôts ETF, photos du max pain des options, relevés du marché global). |
market | string | Le marché de la donnée : tiré du chemin sur les routes qui en servent plusieurs (all sans suffixe = spot + futures perpétuels, spot, futures), fixe sur les routes qui n’en servent qu’un (futures sur le funding, l’open interest, les liquidations et trades/large ; futures, ou spot pour un stablecoin, sur le spread inter-places). |
data | object | array | null | La charge utile : un objet sur une route « dernière valeur », une liste (la plus récente d’abord) sur une route /history, null quand il n’y a rien à servir — unavailable dit alors pourquoi. |
next | string | null | Sur les routes paginées : l’URL relative de la page suivante, prête à appeler (null une fois la plage lue). Voir Paginer un historique. |
Les sondes, la route POST et la forme des données
Les sondes utilisent la même enveloppe : /v1/health (data_type: health, tout sous data) et /v1/status (data_type: status ; son status vaut ok comme toute réponse réussie, l’état des flux est data.state, ok ou degraded). POST /v1/indicators utilise l’enveloppe comme toute autre route ; son data porte un objet indicators indexé par vos identifiants. Une route « dernière valeur » sert toujours un objet (ou null) — /v1/klines est la dernière bougie close —, une route /history toujours une liste. Les seules autres listes sont des collections, une ligne par élément : /v1/symbols, /v1/macro/series, /v1/macro/intraday/series, /v1/etf/fundamentals (une ligne par émetteur), /v1/options/max-pain, /v1/options/expiry, /v1/options/oi/distribution, et les séries d’événements (/v1/trades/spot, /v1/trades/futures, /v1/trades/large, /v1/trades-kline/futures/seconds, /v1/liquidations/events). La référence interactive donne le schéma exact de chaque route.
#Réduire la réponse
Le paramètre ?fields= restreint data aux champs demandés : une liste séparée par des virgules, en notation pointée pour les champs imbriqués. Sur une série de mille lignes dont vous n’utilisez que deux colonnes, la réponse diminue d’autant.
# The full response
curl -H "X-API-KEY: $BYTNODE_API_KEY" \
"https://api.bytnode.com/v1/basis?symbol=BTCUSDT"
# Two fields only
curl -H "X-API-KEY: $BYTNODE_API_KEY" \
"https://api.bytnode.com/v1/basis?symbol=BTCUSDT&fields=time,basis_pct"
# Dotted notation for a nested field
curl -H "X-API-KEY: $BYTNODE_API_KEY" \
"https://api.bytnode.com/v1/btc/network?fields=difficulty,next_adjustment.estimated_retarget_at"Un champ inconnu est refusé, avec la suggestion du plus proche plutôt qu’un silence :
{
"status": "error",
"server_time": "2026-09-30T16:07:27.808Z",
"error": {
"code": "unknown_field",
"message": "Unknown field 'basis_percent'. Did you mean 'basis_pct'? Accepted fields: basis_pct, basis_value, futures_price, spot_price, time.",
"param": "fields",
"details": {
"suggestion": "basis_pct",
"accepted": [
"basis_pct",
"basis_value",
"futures_price",
"spot_price",
"time"
]
}
}
}#Lire un champ vide
Un null ou une liste vide ne prouve pas l’absence du phénomène : cela peut tout aussi bien vouloir dire que le phénomène n’existe pas pour cet actif. Confondre les deux mène à des affirmations fausses, comme « aucune liquidation » alors que l’actif est un stablecoin, qui n’a pas de marché à terme.
Chaque route lève l’ambiguïté avec la clé unavailable : quand data est vide (null ou une liste vide), la réponse dit pourquoi — sur une route REST comme sur chaque champ du snapshot. Testez unavailable, pas le vide de data :
| Raison | Type | Description |
|---|---|---|
not_applicable | structural | La métrique n’a pas de sens ici : un champ dérivé du marché à terme sur un stablecoin spot. Aucune collecte n’y changerait rien. |
no_api_key | configuration | La source amont n’est pas branchée sur cette installation : la donnée n’a jamais été collectée. C’est un manque d’infrastructure, pas un fait de marché. |
no_data | market | Une vraie absence de données sur la fenêtre demandée. Le seul cas dont on peut tirer une conclusion. |
Le détail de ces clés sur le snapshot est sur la page Snapshot. Pour savoir à l’avance ce qu’un actif peut servir, interrogez son rapport de capacités.
#Les erreurs
Toute erreur a la même forme : status: "error", server_time et un objet error (code, stable et lisible par une machine ; message, en anglais ; param quand un paramètre est en cause). Les refus liés à l’offre portent aussi l’en-tête X-Deny-Reason, dont la valeur est l’error.code (missing_api_key, invalid_api_key, plan_family, plan_websocket, rate_limited, quota_exceeded, history_depth) ; les refus temporels ajoutent error.details.retry_after_s dans le corps et Retry-After en en-tête, avec la même valeur : l’attente jusqu’à la prochaine requête permise sur le débit, les secondes jusqu’à la remise à zéro sur le quota mensuel. Toute réponse, erreurs comprises, porte un en-tête X-Request-Id : citez-le quand vous signalez un problème. error.details, présent quand l’erreur porte des données structurées, donne les mêmes choses pour le code : suggestion (la valeur acceptée la plus proche), accepted (les valeurs acceptées), la fenêtre de votre offre sur un history_depth.
{
"status": "error",
"server_time": "2026-09-30T16:07:27.808Z",
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded: your plan allows a limited number of requests per minute. Retry after the Retry-After delay.",
"details": {
"retry_after_s": 2
}
}
}| Code | Cause la plus fréquente | Que faire |
|---|---|---|
401Clé d’API refusée | La clé d’API est absente, invalide ou révoquée (error.code : invalid_api_key). La réponse porte WWW-Authenticate: ApiKey header="X-API-KEY". | Vérifiez que l’en-tête X-API-KEY est bien envoyé : certains clients HTTP le perdent après une redirection. Créez ou copiez une clé depuis la console. |
403Refusé par l’offre | La clé est valide mais l’offre ne couvre pas la requête ; l’en-tête X-Deny-Reason porte le même code que error.code : plan_family (famille de données de marché hors offre), plan_websocket (flux WebSocket hors offre) ou history_depth (une profondeur d’historique au-delà de la fenêtre de l’offre). | Sur plan_family et plan_websocket, la donnée existe mais n’est pas dans votre offre. Sur history_depth, error.message donne la limite maximale à utiliser. |
404Route inconnue | Le chemin n’existe pas (error.code : not_found). Le message propose la route la plus proche quand il y en a une. | Vérifiez le chemin : le message donne la route la plus proche quand il y en a une ; toutes les routes sont dans le contrat. |
405Méthode non autorisée | La route existe, mais pas avec cette méthode (error.code : method_not_allowed) ; l’en-tête Allow liste celles qu’elle accepte. | Utilisez GET (POST seulement pour /v1/indicators) ; HEAD est accepté sur toute route GET, et OPTIONS (le préflight CORS) sur toute route. |
422Paramètre manquant, inconnu ou invalide | Un symbole, un actif ou une série inconnus ou désactivés, une profondeur au-delà du plafond du champ, un paramètre d’indicateur hors bornes, symbol manquant, unité de temps hors liste, champ multi-unités demandé sans le suffixe @tf, champ inconnu dans ?fields=, paramètre que la route ne déclare pas ou paramètre répété, since_ms ou until_ms exprimé en secondes ou hors plage, corps JSON invalide. | Sur un champ ou un paramètre inconnu, la réponse suggère le nom le plus proche. Sur une unité de temps, elle liste les valeurs acceptées. |
429Débit ou quota dépassé | Soit plus de requêtes par minute que votre offre ne l’autorise, ou une rafale trop dense (X-Deny-Reason: rate_limited, Retry-After : l’attente du prochain jeton) ; soit le quota mensuel du compte est épuisé (X-Deny-Reason: quota_exceeded, Retry-After : les secondes jusqu’à sa remise à zéro). | Sur rate_limited, attendez les secondes indiquées par Retry-After. Sur quota_exceeded, le compteur repart le premier jour du mois (UTC) ; vous pouvez aussi changer d’offre depuis la console. |
503Indisponibilité momentanée | Une dépendance de lecture redémarre. À la poignée de main WebSocket, le service de flux a atteint sa capacité, tous comptes confondus : ce n’est pas le plafond de connexions de votre offre, qui ferme en 4003. | Réessayez après le délai porté par Retry-After, ou avec un délai croissant s’il est absent. L’erreur est transitoire par construction. |
500Erreur inattendue | Une panne côté service, jamais causée par la requête. | Réessayez ; si la réponse persiste, signalez-la avec l’en-tête X-Request-Id de la réponse. |
504Requête trop longue | La requête a pris trop de temps pour la fenêtre ou la profondeur demandée (error.code : upstream_timeout). | Réduisez limit ou la plage de temps (since_ms / until_ms), puis réessayez : rejouer la même requête prendrait le même temps. |
La page Codes d’erreur reprend chaque cas avec les messages exacts et les erreurs propres aux indicateurs et au flux temps réel.
#En-têtes de réponse
Toute réponse faite avec une clé comptée (toute clé créée dans la console) porte les en-têtes de débit, au format standard de l’IETF : la politique de votre offre et ce qu’il en reste, quota mensuel compris. La console montre le même état, et prévient à 80 % puis à la première requête refusée. Deux autres en-têtes n’apparaissent que sur un refus.
| En-tête | Type | Description |
|---|---|---|
X-Deny-Reason | string | La raison d’un refus lié à l’offre : rate (limite de débit), quota (quota mensuel), family (famille hors offre), websocket (flux hors offre), key (clé absente, invalide ou révoquée) ou depth (historique au-delà de la fenêtre de l’offre). |
Retry-After | integer | Présent sur un 429, et sur un 503 quand un délai de nouvel essai est connu. Le nombre de secondes à attendre avant de réessayer : 1 sur un débit dépassé, sur un quota mensuel épuisé, les secondes jusqu’à sa remise à zéro (le premier jour du mois, UTC). Respectez-le plutôt que d'inventer un délai ; en son absence, espacez progressivement les essais. |
RateLimit-Policy | string | Sur toute réponse faite avec une clé comptée. Les politiques de votre offre, dont les noms sont des identifiants fixes : "rate" (le seau de jetons du débit par minute : q sa capacité de rafale, w les secondes pour le remplir à vide) et "quota" (le quota mensuel : q requêtes par mois, w la durée du mois civil en cours, en secondes). Exemple pour l’offre Traders un mois de 31 jours : "rate";q=20;w=20, "quota";q=200000;w=2678400. |
RateLimit | string | Sur toute réponse faite avec une clé comptée. Ce qu’il reste de chaque politique : r les requêtes restantes (le r de "quota" est le quota mensuel restant) et t les secondes avant sa restauration complète (pour "quota" : jusqu’au 1er du mois suivant, 00:00 UTC). Exemple : "rate";r=19;t=1, "quota";r=187342;t=1641600. Les mêmes valeurs en JSON : GET /v1/limits. |
Cache-Control | string | Sur toute réponse de données : private, max-age=1 au plus — une réponse a en général jusqu’à une seconde de retard (une copie en cache peut parfois atteindre deux secondes). La fréquence à laquelle la SOURCE d’une route change (de 2 s pour les trades individuels à 5 min pour les séries macro, on-chain et ETF journalières) figure dans la référence interactive : interroger plus souvent rend les mêmes valeurs. L’en-tête X-Cache-Status (HIT, MISS, EXPIRED, UPDATING) dit si la réponse vient du cache d’une seconde de la passerelle : il est informatif, jamais à tester dans votre code. |