Erreur 400 “Invalid JSON” sur le jeton d'accès Tradovate
Vous envoyez des identifiants corrects à l'endpoint access-token et recevez quand même un 400 “invalid JSON”. Voici toutes les erreurs de format qui le provoquent, et la requête corrigée pour curl, Python et JavaScript.
Vous envoyez votre identifiant et votre mot de passe à l'endpoint access-token, et au lieu d'un jeton vous recevez un abrupt 400 avec un message du type [Invalid JSON: expected '}' or ',', offset: 0x00000028]. Les identifiants sont corrects. Le compte fonctionne dans la plateforme web. Alors qu'est-ce qui cloche ?
Voici la réponse courte : le serveur n'a jamais lu votre connexion. Un 400 “invalid JSON” survient au stade de l'analyse, avant toute vérification des identifiants. Quelque chose dans la forme de votre corps de requête, les guillemets, l'en-tête, la façon dont votre langage l'a sérialisé, n'est pas du JSON valide. Corrigez le format et les mêmes identifiants passeront sans problème.
Ce guide passe en revue chaque version de cette erreur de formatage, dans l'ordre où vous êtes le plus susceptible de la rencontrer, avec la requête corrigée pour curl, Python et JavaScript.
À quoi ressemble réellement l'erreur
La requête est envoyée à l'endpoint d'authentification. Sur l'environnement démo, c'est :
POST https://demo.tradovateapi.com/v1/auth/accesstokenrequest
En réel, c'est https://live.tradovateapi.com/v1/auth/accesstokenrequest. Même corps, hôte différent, les confondre est un problème à part, mais cela ne produira pas de message “invalid JSON”, alors mettons cela de côté pour l'instant.
Un appel réussi renvoie un objet JSON avec un accessToken et une expirationTime. Un appel malformé renvoie un HTTP 400 et un corps qui nomme la plainte de l'analyseur ainsi qu'un décalage en octets. Ce décalage est votre meilleur indice, nous y reviendrons.
La cause réelle : votre corps n'est pas du JSON valide
JSON a des règles strictes que beaucoup de code enfreint silencieusement. Les façons les plus courantes dont un corps de connexion devient invalide :
- Guillemets simples. JSON exige des guillemets doubles autour des clés et des valeurs de type chaîne. Si vous imprimez un dict Python, un objet JavaScript ou un hash Ruby directement dans la requête, vous obtenez des guillemets simples et ce n'est plus du JSON.
- Une virgule finale. Une virgule après le dernier champ est légale dans la plupart des langages et illégale en JSON.
- Encodage de formulaire. De nombreuses bibliothèques HTTP utilisent par défaut
application/x-www-form-urlencoded. Le serveur, qui attend du JSON, s'étouffe dès le tout premier octet. - Double encodage. Vous sérialisez l'objet une fois en chaîne, puis remettez cette chaîne à un client qui la sérialise à nouveau. Le corps est maintenant une chaîne entre guillemets, pas un objet.
- Caractères cachés. Une marque d'ordre des octets ou un “guillemet typographique” collé depuis un document ou un chat semble identique à l'écran mais casse l'analyseur.
Chacun de ces cas produit la même famille d'erreurs 400. Éliminons-les un par un.
Solution 1 : envoyez du vrai JSON à guillemets doubles
Commençons par la cible. Voici à quoi ressemble un corps valide, des guillemets doubles partout, pas de virgule finale, cid comme simple nombre :
{ "name": "your_username", "password": "your_password", "appId": "My App", "appVersion": "1.0", "cid": 8, "sec": "your-api-secret", "deviceId": "123e4567-e89b-12d3-a456-426614174000" }
Seuls name et password sont strictement obligatoires ; le reste identifie votre application et est nécessaire pour un accès API complet. Comparez cela à la version cassée que les gens envoient habituellement, un objet de langage imprimé sous forme de texte :
{'name': 'your_username', 'password': 'your_password', 'appId': 'My App', 'cid': '8'}
Deux problèmes ici : des guillemets simples partout, et cid entouré de guillemets, ce qui le fait lire comme une chaîne. Remplacez les guillemets simples par des doubles et retirez les guillemets de la valeur cid, et le corps devient valide.

Solution 2 : définissez l'en-tête Content-Type
Un JSON valide dans le corps ne suffit pas. Vous devez aussi indiquer au serveur que le corps est du JSON. Ajoutez ces en-têtes à la requête :
Content-Type: application/jsonAccept: application/json
Sans Content-Type: application/json, la plupart des clients basculent vers l'encodage de formulaire et le serveur tente d'analyser les champs de formulaire comme un document JSON. L'analyse échoue à l'octet zéro et vous obtenez un 400. Cet en-tête à lui seul résout une part surprenante des rapports “invalid JSON”.
Solution 3 : en Python, utilisez json= et non data=
La bibliothèque requests fait constamment trébucher les gens ici. Si vous passez votre dictionnaire via le paramètre data, il est encodé en formulaire et les guillemets doubles n'apparaissent jamais. Passez-le plutôt via json, cela sérialise le dict en JSON correct et définit automatiquement l'en-tête Content-Type.
Incorrect :
requests.post(url, data=credentials)
Correct :requests.post(url, json=credentials)
Avec json=credentials, vous ne touchez jamais aux guillemets à la main, la bibliothèque s'en occupe correctement. Assurez-vous simplement que credentials est un vrai dict (avec cid comme int), pas un blob déjà converti en chaîne. Si vous avez déjà appelé json.dumps() dessus, soit vous supprimez cela et utilisez json=, soit vous gardez la chaîne et la passez via data= avec l'en-tête Content-Type défini manuellement. Ne faites pas les deux.

Solution 4 : en JavaScript, sérialisez exactement une fois
Avec fetch, l'erreur classique est d'oublier JSON.stringify (le corps devient alors la chaîne inutile [object Object]) ou de l'appeler deux fois. Faites-le une fois et définissez l'en-tête :
fetch(url, {method: "POST",headers: { "Content-Type": "application/json", "Accept": "application/json" },body: JSON.stringify(credentials)})
Si credentials est déjà une chaîne, ne l'enveloppez pas à nouveau dans JSON.stringify, le second passage échappe chaque guillemet et le serveur reçoit une longue chaîne entre guillemets, ce qui n'est pas un objet.
Solution 5 : avec curl, échappez vos guillemets
Le shell avale les caractères guillemets, un payload JSON brut en ligne de commande demande donc de la prudence. Échappez les guillemets doubles internes avec des antislashs :
curl -X POST https://demo.tradovateapi.com/v1/auth/accesstokenrequest \-H "Content-Type: application/json" \-H "Accept: application/json" \-d "{ \"name\": \"your_username\", \"password\": \"your_password\", \"appId\": \"My App\", \"appVersion\": \"1.0\", \"cid\": 8, \"deviceId\": \"123e4567-e89b-12d3-a456-426614174000\", \"sec\": \"your-api-secret\" }"
Une voie plus simple : placez le JSON dans un fichier et passez -d @body.json, ce qui évite entièrement le problème des guillemets du shell. Notez que le simple -d utilise par défaut l'encodage de formulaire, l'en-tête Content-Type: application/json doit donc rester explicite.
Solution 6 : vérifiez les types de vos champs
Même un JSON propre est rejeté si une valeur a le mauvais type. Celui qui piège le plus souvent est cid : c'est un entier, envoyez donc "cid": 8, jamais "cid": "8". Gardez les champs de type chaîne (name, password, appId, appVersion, sec, deviceId) entre guillemets, et laissez cid comme simple nombre. Voici la référence rapide :
| Champ | Type | Obligatoire ? | Exemple |
|---|---|---|---|
| name | string | Oui | "trader_jane" |
| password | string | Oui | "S3cureP@ss!" |
| appId | string | Pour un accès complet | "My App" |
| appVersion | string | Pour un accès complet | "1.0" |
| cid | integer | Pour un accès complet | 8 |
| sec | string | Pour un accès complet | "f03741b6-..." |
| deviceId | string | Recommandé | "123e4567-..." |
Si votre mot de passe contient un guillemet double, un antislash ou un saut de ligne, ces caractères doivent être échappés à l'intérieur de la chaîne JSON. Laisser votre sérialiseur construire le corps (les approches json= et JSON.stringify ci-dessus) s'en charge pour vous.
Lire le décalage dans l'erreur
Lorsque le message vous fournit un décalage comme 0x00000028, utilisez-le. La valeur est hexadécimale, 0x28 vaut 40 en décimal, et pointe vers l'octet où l'analyseur s'est arrêté. Comptez dans le corps exact que vous avez envoyé (enregistrez-le, ne devinez pas) et inspectez ce caractère. Un guillemet simple, une virgule sans rien après, ou un caractère de contrôle s'y trouve presque toujours. Un décalage de 0x0 est différent : cela signifie que l'analyseur n'a rien trouvé à lire, donc votre corps était vide ou n'a jamais quitté le client.
Vérifiez la solution
Une fois le corps propre et l'en-tête défini, relancez la requête. Un appel fonctionnel renvoie un HTTP 200 avec un payload JSON contenant votre accessToken, un userId, et une expirationTime d'environ 90 minutes. Copiez ce jeton dans un en-tête Authorization: Bearer <token> pour chaque appel suivant.

Si vous continuez à obtenir le même 400, enregistrez les octets bruts de ce que vous envoyez réellement, pas ce que vous pensez envoyer, et comparez-le à l'exemple valide ci-dessus. La différence est presque toujours un guillemet ou une virgule.
Quand le JSON est propre et que ça échoue quand même
Si l'analyseur est satisfait mais que la requête échoue toujours, vous avez dépassé le problème de format et vous êtes face à un problème d'authentification ou d'environnement :
- Mauvais hôte. Des identifiants démo sur l'hôte réel (ou l'inverse) échouent même avec un JSON parfait.
- Mauvais secret API ou cid. Un
secoucidincorrect renvoie un 400 qui n'a rien à voir avec le format, le corps a été analysé correctement, les valeurs ne correspondent tout simplement pas. - Limite de session ou limite de débit. Bombarder l'endpoint peut vous bloquer l'accès. Si vous voyez des échecs répétés sous charge, lisez notre note sur la limite de débit 429 de l'API Tradovate.
- Jeton expiré lors d'appels ultérieurs. Le jeton lui-même ne dure qu'environ 90 minutes ; renouvelez-le avant son expiration plutôt que de vous réauthentifier à chaque fois. Voir 401 non autorisé de l'API Tradovate (jeton expiré).
Simplifiez l'authentification avec PickMyTrade
Vous préférez ne pas surveiller les requêtes de jeton, les expirations de 90 minutes et le guillemettage JSON ? PickMyTrade connecte votre compte Tradovate et achemine vos alertes TradingView vers des ordres réels, l'authentification et la gestion des sessions fonctionnent en coulisses, il n'y a donc aucun code d'authentification à déboguer pour vous.
Faites l'impasse totale sur le code d'authentification
PickMyTrade connecte votre compte Tradovate et gère les requêtes de jeton, les renouvellements et le formatage JSON en coulisses, afin que vos alertes s'acheminent sans que vous ayez à déboguer le moindre appel d'authentification.
Démarrez votre essai gratuit de 5 joursQuestions fréquentes
Le 400 est déclenché avant même que le serveur vérifie votre connexion. Le corps n'est pas du JSON valide, ou n'a pas été envoyé comme du JSON. Recherchez des guillemets simples, une virgule finale, un encodage de formulaire, ou un en-tête Content-Type: application/json manquant. Corrigez le corps et les mêmes identifiants s'authentifient sans problème.
C'est la position en octets où l'analyseur JSON s'est arrêté, écrite en hexadécimal. 0x28 vaut 40 en décimal, cherchez donc autour du 40e caractère du corps exact que vous avez envoyé, c'est là que la syntaxe a été rompue. Un décalage de 0x0 signifie que le corps était vide ou n'est jamais arrivé.
Un nombre. Envoyez "cid": 8, pas "cid": "8". Le mettre entre guillemets transforme un champ entier en chaîne et peut faire rejeter la requête même lorsque le JSON est par ailleurs valide.
L'explorateur construit un corps propre à guillemets doubles et définit l'en-tête pour vous. Votre client peut imprimer un objet de langage avec des guillemets simples, encoder les données en formulaire, ou encoder la chaîne deux fois. Copiez le corps exact de l'explorateur et faites-le correspondre octet par octet.
Oui. Échappez les guillemets doubles internes avec des antislashs, ou enveloppez tout le payload entre guillemets simples pour que le shell ne touche pas aux guillemets doubles internes. Passer le corps depuis un fichier avec -d @body.json évite entièrement ce jeu de guillemets.
Ce guide est fourni à des fins éducatives et informatives uniquement et ne constitue pas un conseil financier, en investissement ou en trading. Le trading de contrats à terme et d'autres produits à effet de levier comporte un risque de perte important et ne convient pas à tous les investisseurs. PickMyTrade est une plateforme d'automatisation tierce indépendante et n'est ni affiliée, ni approuvée, ni sponsorisée par Tradovate, Inc. ou Bookmap. Tous les noms, logos et marques associés sont la propriété de leurs détenteurs respectifs. Les fonctionnalités et les étapes de la plateforme évoluent avec le temps ; confirmez toujours le processus actuel dans la documentation officielle de la plateforme avant d'agir.