Tradovate API

Tradovate user/syncrequest WS 401 Access Denied

You open a socket, fire user/syncrequest, and get a flat 401 Access is denied back. The socket was never authorized, here's the exact handshake that fixes it.

Reviewed by the PickMyTrade Trading Systems Team Last updated
· 7 min read
Tradovate WebSocket frame log showing user/syncrequest returning a 401 Access is denied response

You opened a WebSocket, fired off user/syncrequest, and instead of a stream of account and order data you got a flat 401 with “Access is denied.” Nothing is wrong with your login and nothing is broken on the account. The socket simply was never authorized, so the server treats every request on it as anonymous. The fix is a specific order of operations: pull an access token from the REST auth endpoint, hand it to the socket in an authorize frame, wait for the success reply, and only then send user/syncrequest. Get that sequence right and the 401 disappears.

This one trips up almost everyone the first time they wire up the Tradovate socket by hand, usually because they try to authenticate the way REST works. WebSockets don't work that way here. Let's walk through exactly what's happening and the clean path out.

What the error actually looks like

In your frame log you'll see the socket connect, the open frame arrive, then your user/syncrequest come straight back with a denial. It reads something like this:

<-- o
--> user/syncrequest
1

<-- a[{"i":1,"s":401,"d":"Access is denied"}]

The giveaway is the status 401 paired with Access is denied on the request frame. If you never saw a status-200 reply to an authorize frame before this, that's your answer: the socket isn't authenticated.

Why the socket throws a 401

There are really only a handful of root causes, and they stack up in a predictable order.

1. The socket was never authorized

This is the big one. A raw WebSocket connection to Tradovate is unauthenticated. Opening it and immediately sending user/syncrequest is like walking into a members' club and ordering a drink without showing your card. You have to send exactly one authorize frame per connection, carrying a valid access token, before anything else will go through.

2. You tried to get the token over the socket

A common wrong turn is trying to call accessTokenRequest through the WebSocket itself. That endpoint lives on the REST side. The token is minted over HTTPS, then presented to the socket. The socket never issues tokens.

3. An environment parameter in the token request

If you copied a request body from somewhere and it includes "environment": "demo" (or "live"), drop it. That field isn't accepted by accessTokenRequest and can knock the whole request sideways. The environment is decided by the host you call, not by a parameter.

4. A live account with no deviceId or an unapproved device

Demo is lenient. Live is not. On a funded/live account you need to send a stable deviceId when you request the token, and the device has to be approved through the confirmation flow Tradovate runs for two-factor security. Miss that and the live token you get back won't authorize the live socket.

5. The token expired

If everything worked earlier and then started returning 401 out of nowhere, the token aged out. Access tokens are time-limited, so a long-lived socket will eventually see a 401 as though it was never authorized. That's a renewal problem, not a setup problem.

The fix, step by step

1

Get an access token from REST

POST your credentials to the auth endpoint over HTTPS. Use the host that matches the account you want:

POST https://demo.tradovateapi.com/v1/auth/accessTokenRequest   (simulated)
POST https://live.tradovateapi.com/v1/auth/accessTokenRequest   (funded)

Content-Type: application/json
{
  "name": "your-username",
  "password": "your-password",
  "appId": "YourAppName",
  "appVersion": "1.0",
  "cid": 0000,
  "sec": "your-api-secret",
  "deviceId": "a-stable-unique-id"
}


The response hands you an accessToken string (plus an expiration timestamp). That token is what the socket wants. Notice there is no environment field in that body, the demo host gives you a demo token, the live host gives you a live token.

2

Open the socket and wait for the open frame

Connect to the WebSocket URL that matches your token's environment:

wss://demo.tradovateapi.com/v1/websocket   (simulated)
wss://live.tradovateapi.com/v1/websocket   (funded)


The moment the connection is up, the server sends a single-character open frame: o. Don't send anything until you've seen it. A demo token on the live socket (or the reverse) is its own quiet way to earn a 401, so keep the pair matched.

3

Send the authorize frame

Frames on this socket are plain text: an endpoint, a request id, a blank line for the query string, then the body, each separated by a newline. The authorize frame puts the token in the body:

authorize
0

YOUR_ACCESS_TOKEN


Written as a single string that's authorize\n0\n\nYOUR_ACCESS_TOKEN. The server answers with a data frame and a status you actually care about:

a[{"i":0,"s":200}]

Status 200 means the socket is now authenticated for the life of the connection. If you get anything other than 200 here, stop, the token is the problem, and syncing won't fix itself further down the line.

4

Now send user/syncrequest

With the socket authorized, the same call that was failing goes right through. Send it with the next request id and an empty body to sync everything the user has access to:

user/syncrequest
1



That's user/syncrequest\n1\n\n. You'll get back the initial snapshot of accounts, positions, orders, fills and cash balances, and from then on the socket streams incremental updates as they change. user/syncrequest is WebSocket-only, there's no REST equivalent, which is exactly why the socket has to be authorized first.

5

Keep the connection alive

Once authorized, send a heartbeat frame, an empty JSON array, [], every couple of seconds (roughly every 2.5 seconds) so the server doesn't drop you. The server sends its own heartbeats too. If you stop hearing from it for around ten seconds, treat the socket as dead, reconnect, and run the authorize step again on the fresh connection.

REST POST to accessTokenRequest returning an accessToken, with no environment parameter in the bodyWebSocket authorize frame carrying the access token followed by a status 200 success response

Live accounts: the deviceId and permission check

If demo works flawlessly and live is the only place you hit the 401, the cause is almost always on the device-approval side rather than your code. Two things to confirm.

First, make sure API access is actually enabled on the account and the key has permission to do what you're asking. In the Tradovate web app this lives under the account settings, in the section that manages the API Access add-on and key permissions. (Confirm the exact label in your current version, the wording and location of this add-on screen get tweaked over time.)

Tradovate account settings showing the API Access add-on and API key permission controls

Second, send a deviceId that stays the same across runs and approve it. Live enforces device-based two-factor security; a brand-new or missing deviceId triggers an approval step that, until you clear it, leaves the token unable to authorize the live socket. Generate one stable identifier for your app, reuse it every time, and confirm it through the email Tradovate sends.

Still getting 401? Run this checklist

Check What to confirm
Authorize came firstYou sent one authorize frame and saw s:200 before any other request.
Token sourceToken came from REST /auth/accessTokenRequest, not from the socket.
No environment fieldThe token request body has no environment parameter.
Host matches tokenDemo token on the demo socket, live token on the live socket, never crossed.
deviceId (live)A stable deviceId was sent and the device is approved.
Token freshnessThe token hasn't expired since you fetched it.
Key permissionsAPI access is enabled and the key is allowed to read/route on that account.

If it worked and then broke mid-session, jump straight to token lifetime. Access tokens don't last forever, and a socket that's been open for a while will start throwing 401s the moment the token behind it lapses. Renew ahead of expiry instead of waiting for the failures. And if the very first token request is what's failing, back up to getting API access and generating a key before you touch the socket at all.

Skip the socket plumbing entirely

Hand-rolling the auth handshake, heartbeats, device approval and token renewal is a lot of moving parts just to route an order. If you're wiring Tradovate to TradingView alerts or a strategy, PickMyTrade handles the whole connection layer for you, authorized sessions, live device handling and token refresh included, so you send a signal and it trades.

Skip the Socket Plumbing

PickMyTrade handles the whole connection layer for you, authorized sessions, live device handling and token refresh included, so you send a signal and it trades.

Start Your Free 5-Day Trial

Frequently Asked Questions

Because the WebSocket connection was never authorized. user/syncrequest only runs over an authorized socket. Send an authorize frame carrying a valid access token first, wait for the status-200 reply, then send syncrequest.

From the REST endpoint /auth/accessTokenRequest, not from the socket. POST your credentials over HTTPS, read accessToken out of the JSON response, and hand that token to the socket in the authorize frame.

No. environment isn't a valid field for that request and can cause it to fail. Demo versus live is decided by which host you call, the demo host for the simulated account, the live host for the funded one.

Live enforces device approval that demo skips. Send a stable deviceId when you request the live token, approve the device from the confirmation Tradovate emails you, and make sure you're authorizing the live socket with a live token.

The token expired. Tokens are time-limited, so a long-running socket eventually hits a 401 as if it was never authorized. Renew the token before it lapses and re-authorize on a fresh session.

This guide is for educational and informational purposes only and is not financial, investment, or trading advice. Trading futures and other leveraged products carries a substantial risk of loss and is not suitable for every investor. PickMyTrade is an independent third-party automation platform and is not affiliated with, endorsed by, or sponsored by Tradovate, Inc. or Bookmap. All related names, logos, and trademarks are the property of their respective owners. Platform features and steps change over time, so always confirm the current process in the official platform documentation before acting.