401 error in live mode

I can test my app just fine but when it comes to live mode, I keep getting 401 errors.

The only clue that I can find is this forum thread: 401 access denied in /order/placeorder

I am making a post request to this end point

{"name": "username", "password": "password", "appId": "from_backend", "appVersion": "from_backend", "deviceId": 72572238561283, "cid": "from_backend", "sec": "xxx-xxx-xxx-xxx-xxx"}

is there a problem with the deviceId format?

After authorization I can call the contract/find endpoint to lookup symbols but when I try place order it fails. The same place order command works in demo mode.



[{'s': 401, 'i': 2, 'd': 'Access is denied'}]

Did you get an accessToken? That token is used in any subsequent requests. You’re not supposed to be sending the password in the message. accountId is separate even from the accessToken.

It’s also possible that there is confusion between websocket endpoints and REST endpoints. Make sure to be crystal clear which you’re using. The docs aren’t very clear in this regard.

The first code sample is the post request to get access token. The subsequent code samples are requests to the websocket api. accountId should be a number not password and I’ll change that.

I haven’t looked at the format of the other parts of your request, but at first glance, your device id looks incomplete. You can get your id by creating a new DeviceUUID object and calling the get method on it.

I just checked mine, and it registers as 71986488-882c-43db-c345-92a0a2ed9329, with a couple digits and chars altered for security.

Also, all placeOrder requests should be submitted via POST to the REST URL, not the webSocket.

You could use order/placeOrder from the websocket, I think the Trader application does it this way in fact, so no issue there. Definitely look into how you’re using device ID because using device ID is strictly enforced on the live environment. Also ensure you’re using the correct endpoint (if you’re on live, use live.tradovateapi.com, etc)

1 Like

Does the deviceId have to be uuid format like this:

const body = {
    name:       "<replace with your credentials>",
    password:   "<replace with your credentials>",
    appId:      "Sample App",
    appVersion: "1.0",
    cid:        8,
    sec:        "f03741b6-f634-48d6-9308-c8fb871150c2",
    deviceId:   "123e4567-e89b-12d3-a456-426614174000"


``deviceId is a string up to 64 characters that uniquely and permanently identify the physical device.

I just tried again, removed some info for security purposes. I am still getting 401 error.

__TV_API -- get_endpoint -- LIVE URL -- [https://live.tradovateapi.com/v1]
__TV_API -- get_endpoint -- header  | {'content-type': 'application/json', 'accept': 'application/json'}
__TV_API -- get_endpoint -- payload | {"name": "username", "password": "password", "appId": "app_id", "appVersion": "version", "deviceId": "f000f3a4-5e00-3a00-00a6-4a7f00a438be", "cid": "012", "sec": "sec"}
__TV_API -- get_endpoint -- url     | https://live.tradovateapi.com/v1/auth/accesstokenrequest

@Alexander do you notice anything wrong with the parameters? They work just fine with demo credentials.

I have a data structure that holds the relevant websocket endpoints, they look okay to me. I can connect to the live websockets and i can also authorize on both the marketdata and api websockets but I just keep getting 401 errors when trying to place order.

hasMarketData': True, 'outdatedLiquidationPolicy': False, 
'api_wss': 'wss://live.tradovateapi.com/v1/websocket', 
'md_wss': 'wss://md.tradovateapi.com/v1/websocket', 
'api_url': 'https://live.tradovateapi.com/v1/

The device ID doesn’t have to be formatted any way, but you should use some kind of UUID because you don’t want any device to be able to be identified as another one. Another thing I saw was the "cid" field - try using a number (not a string) for the "cid" field if you aren’t already.

The other thing I have to ask is, do you have a market data subscription? In order to trade live you must have one.

I do have active subscription, I am using a uuid style string. and cid is an integer.

is there anything else that you can suggest?

Ok… so. some more things to check.

When you’re in the api section of tradovate.com and you create an api key…

Does your program match what’s in the website:?
appId, cid, and sec?

Look at the curl request to confirm. (sec wont show there)

My login looks like:

            let response = await this.axios.post('auth/accesstokenrequest', {
			  	name: 		process.env.TRADOVATE_name,
				password: 	process.env.TRADOVATE_password,
				appId: 		process.env.TRADOVATE_appId,
				appVersion: process.env.TRADOVATE_appVersion,
				deviceId: 	process.env.TRADOVATE_deviceId,
				cid: 		process.env.TRADOVATE_cid,
				sec: 		process.env.TRADOVATE_sec
			this.accessToken   = response.data.accessToken
			this.mdAccessToken = response.data.mdAccessToken

		//set into the header
		this.axios.defaults.headers = {...config.headers,
		    Authorization: `Bearer ${this.accessToken}`
		log.verbose(`TradovateTrader:init Axios headers: ${JSON.stringify(this.axios.defaults.headers)}`)

A market order in my code looks like:

        const marketOrder = {
            accountId: Number(accountId),
            accountSpec: process.env.TRADOVATE_name,
            orderQty: size, isAutomated: true, orderType: "Market"

The constructed string of that send looks like:


You need to get token… initialize the socket, on message send authorize for socket… handle messages… and send order whenever you want. Maybe the following will help

        const ws = new WebSocket(process.env.TRADOVATE_ACCOUNT_URL)

        ws.onopen = () => {
            log.info('TradovateAccountStream:Stream Connected...')

        ws.onmessage = async ({data}) => {
            log.info(`TradovateAccountStream onmessage: ${data}`)
            if(data == 'o'){
                log.info(`TradovateAccountStream:Received Open Response.. sending:authorize token`)
                const message = `authorize\n${++this.messageCount}\n\n${this.token}`
                log.info(`TradovateAccountStream:SEND: ${message}`)

            } else if ("h" === data[0]){
                ws.send(JSON.stringify([])) //send heartbeat

            } else if ("a" === data[0]) {
                let messages
                try {
                    const t = data.substring(1) //remove first 'a' char..should be an array left
                    messages = JSON.parse(t)  //array of 1-many messages
                } catch (e) {
                    log.error(`TradovateAccountStream:InvalidIncomingJSON ${e}`)
                    throw('AccountStream Parsing Incoming Message Error', e)

                for(const message of messages){
                    log.verbose(`TradovateAccountStream:Received: ${JSON.stringify(message)}`)

Last but not least… urls… notice different urls for login vs websocket



I am not using that example program.

Debugging further, I see that I can subscribe to marketdata and I can also call certain API endpoints


++Sent raw: b"\x81\x9a\xbd\x90bd\xde\xff\x0c\x10\xcf\xf1\x01\x10\x92\xf6\x0b\n\xd9\x9aPn\xd3\xf1\x0f\x01\x80\xdd'7\xf0\xa2"
++Sent decoded: fin=1 opcode=1 data=b'contract/find\n2\nname=MESM2'

++Rcv raw: b'\x81~\x00\x84a[{"s":200,"i":2,"d":{"id":2553027,"name":"MESM2","contractMaturityId":45114,"status":"DefinitionChecked","providerTickSize":0.25}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"s":200,"i":2,"d":{"id":2553027,"name":"MESM2","contractMaturityId":45114,"status":"DefinitionChecked","providerTickSize":0.25}}]'


++Sent raw: b'\x81\xa7g\x85\x0e\xcb\n\xe1!\xb8\x12\xe7}\xa8\x15\xecl\xae6\xf0a\xbf\x02\x8f?\xc1m\xfe,\xb8\x1e\xe8l\xa4\x0b\xa74\xf9R\xb0=\xfbU\xb2s'
++Sent decoded: fin=1 opcode=1 data=b'md/subscribeQuote\n1\n\n{"symbol":2553027}'
MD -- ACTIVE SYMBOL [2553027]

++Rcv raw: b'\x81Ca[{"s":200,"i":1,"d":{"mode":"RealTime","subscriptionId":2553027}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"s":200,"i":1,"d":{"mode":"RealTime","subscriptionId":2553027}}]'
++Rcv raw: b'\x81~\x01\x9aa[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.698Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":27},"Offer":{"price":4372.25,"size":9},"Trade":{"price":4372.25,"size":2},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659127},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.698Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":27},"Offer":{"price":4372.25,"size":9},"Trade":{"price":4372.25,"size":2},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659127},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'

++Rcv decoded: fin=1 opcode=1 data=b'a[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.715Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":27},"Offer":{"price":4372.25,"size":4},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659128},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv raw: b'\x81Ca[{"s":200,"i":2,"d":{"mode":"RealTime","subscriptionId":2553027}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"s":200,"i":2,"d":{"mode":"RealTime","subscriptionId":2553027}}]'
++Rcv raw: b'\x81~\x01\x9aa[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.811Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":27},"Offer":{"price":4372.25,"size":2},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659128},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.811Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":27},"Offer":{"price":4372.25,"size":2},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659128},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv raw: b'\x81~\x01\x9aa[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.885Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":35},"Offer":{"price":4372.5,"size":40},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659128},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.885Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":35},"Offer":{"price":4372.5,"size":40},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659128},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv raw: b'\x81~\x01\x9aa[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.885Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":35},"Offer":{"price":4372.5,"size":40},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659138},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv decoded: fin=1 opcode=1 data=b'a[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.885Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":35},"Offer":{"price":4372.5,"size":40},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659138},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'
++Rcv raw: b'\x81~\x01\x9aa[{"e":"md","d":{"quotes":[{"id":2553027,"timestamp":"2022-03-17T17:00:13.935Z","contractId":2553027,"entries":{"Bid":{"price":4372.0,"size":32},"Offer":{"price":4372.25,"size":8},"Trade":{"price":4372.25,"size":1},"OpeningPrice":{"price":4360.75},"SettlementPrice":{"price":4349.5},"TotalTradeVolume":{"size":659138},"OpenInterest":{"size":61938},"HighPrice":{"price":4374.0},"LowPrice":{"price":4320.5}}}]}}]'

I also looked at the backend of the tradovate website and saw a curl example, there’s a deviceId string here, I tried that but the application still gets 401 errors when trying to place orders.

curl -X POST "https://demo-api.tradovate.com/v1/auth/accesstokenrequest" \
 -H "accept: application/json" \
 -H "Content-Type: application/json" \
 -d "{ \
   \"name\": \"username\", \
   \"password\": \"YourPassword\", \
   \"appId\": \"name\", \
   \"appVersion\": \"0.0.0\", \
   \"deviceId\": \"[THERE'S a DEVICEID STRING HERE\", \
   \"cid\": \"000\", \
   \"sec\": \"YourSecret\" \

application permissions:

contract library: read_only,
account_info: full_access,
alerts: full_access,
Orders: full_access,
user info: full_access,
market data: read_only,
positions: read_only,
account risk settings:
full access,
chat: denied

I can call certain api functions but no order functions, i can subscribe and get marketdata stream, also i don’t use demo-api urls or websockets, I use the ones referenced above.

–edit-- adding sample call to place order


[{'s': 401, 'i': 3, 'd': 'Access is denied'}]

@Alexander, any idea what could be wrong here?

no one told you to? it’s an example… but it’s a much more complete example than you’ve provided. If you want free help perhaps provide a complete example.

You’re using websockets for your quotes… and it looks like you’re submitting an order through websocket per your last bit of code.

It looks like you might be putting your order into your quote websocket url/connection vs account url/connection. From my understanding they are separate. account activity on one… and quotes on another.

You really need to provide a full and complete example since there are a lot of pieces here… Show us… from start to finish… your retrieval of token… through to your order submission. with urls


[{'s': 200, 'i': 3, 'd': [{'id': 0000, 'name': '[name is accounts]', 'userId': 000, 'accountType': 'Customer', 'active': True, 'clearingHouseId': NUM, 'riskCategoryId': NUM, 'autoLiqProfileId': NUM, 'marginAccountType': 'Speculator', 'legalStatus': 'Individual', 'archived': False, 'timestamp': '2022-02-11T16:14:14Z', 'nickname': 'NICK'}]}]


[{'s': 200, 'i': 4, 'd': [{'id': 0000, 'name': '[login_username]', 'timestamp': '2022-02-10T21:19:30.772Z', 'userType': 'Trader', 'email': '***@***.com', 'status': 'Active', 'creationTimestamp': '2022-02-10T21:18:25.053Z', 'professional': False, 'twoFactorAuth': True}]}]


[{'s': 200, 'i': 5, 'd': {'failureReason': 'UnknownReason', 'failureText': 'Access is denied'}}]

I see two name, one from account/list and another from user/list

they both have the same id field but the name is different. using either name I can make most requests, except routing orders in live mode… What could be going on? Can someone with access to the backend take a look and see what’s going on?

My authentication is correct because if I change that part of the code, I get incorrect username or password when trying to authenticate.

__TV_API -- get_endpoint -- LIVE URL -- [https://live.tradovateapi.com/v1]
__TV_API -- get_endpoint -- header  | {'content-type': 'application/json', 'accept': 'application/json'}
__TV_API -- get_endpoint -- payload | {"name": "___", "password": "___", "appId": "___", "appVersion": "___", "deviceId": "d___175-0__2-ba02-b___-d0b2___", "cid": __, "sec": "___"}
__TV_API -- get_endpoint -- url     | https://live.tradovateapi.com/v1/auth/accesstokenrequest
__TV_API -- get_endpoint -- status code 200 -- [200]

the code above returns my access token.

Also here are the permissions from the tradovate backend.

Can I have someone look at the backend and see what’s going on?

Not sure where you found this example but it’s not from any of our current documentation. If you replace the URL in that example with https://live.tradovateapi.comv1/auth/accesstokenrequest it will work.

Please confirm the URLs you are using in your application. If you are using the -api.tradovate.com variations, these are all deprecated. Please use

  • https://demo.tradovateapi.com/v1 for sim,
  • https://live.tradovateapi.com/v1 for live.

For WebSockets, please use

  • wss://demo.tradovateapi.com/v1/websocket for sim,
  • wss://live.tradovateapi.com/v1/websocket for live,
  • wss://md.tradovateapi.com/v1/websocket for market data, and
  • wss://replay.tradovateapi.com/v1/websocket for market replay.

Please ensure that you use our most recent documentation. You can find our complete API documentation at https://api.tradovate.com. Additionally, you can look at example code and other resources at GitHub - tradovate/example-api-js and GitHub - tradovate/example-api-faq.

See attached screenshot.

I am using these urls to connect to the demo websockets

Below is the authentication payload that I get back after authentication. I add a few other key value pairs for the websocket urls.

[{'accessToken': '___', 
'mdAccessToken': '___',
'expirationTime': '2022-03-17T16:45:57.765Z',
'userStatus': 'Active', 
'userId': ___, 
'name': '___', 
'hasLive': True, 
'outdatedTaC': False, 
'hasFunded': True, 
'hasMarketData': True, 
'outdatedLiquidationPolicy': False, 
'api_wss': 'wss://live.tradovateapi.com/v1/websocket', 
'md_wss': 'wss://md.tradovateapi.com/v1/websocket', 
'api_url': 'https://live.tradovateapi.com/v1/'}]

The issue is, only placing orders are failing in live mode, I can get user list, get user accounts, lookup symbols, etc… why is it that only the placing order fails?

Here’s an example of sending orders, keep in mind that this works in demo mode but in live mode returns 200 status but still fails with unknown reason.

API -- last command


You’re certain that you’re providing a deviceId that is recognized? A deviceId must be permanent, and the same each time for a given device. You also must have approved this device for use (via email link). Only live enforces that you use verified deviceIds, this is one of the very few differences between sim and live and is the primary reason that 401s appear on live but not sim.

If you are referring to these steps, I did complete that and have my generated key. If it’s something else then I might have missed that step, can you clarify what you’re referring to.

I copied the deviceId provided by the screenshot below, I didn’t bother creating a new one.

looking at the placeorder documentation

This says symbol is a string type.

  "accountSpec": "string",
  "accountId": 0,
  "clOrdId": "string",
  "action": "Buy",
  "symbol": "string",
  "orderQty": 0,
  "orderType": "Limit",
  "price": 0,
  "stopPrice": 0,
  "maxShow": 0,
  "pegDifference": 0,
  "timeInForce": "Day",
  "expireTime": "2019-08-24T14:15:22Z",
  "text": "string",
  "activationTime": "2019-08-24T14:15:22Z",
  "customTag50": "string",
  "isAutomated": true

I think I am sending an integer because I do a symbol lookup and and use the contract id instead of the string name, you can see it below. Could this be the cause of the failure in live mode but not in the demo mode?

Look at the output below. If I try to place order I access denied, doing user/list and account/list both return as expected. This is all in the same websocket connection, why is only placing order failing…?

API – last command


[{'s': 200, 'i': 3, 'd': {'failureReason': 'UnknownReason', 'failureText': 'Access is denied'}}]

API – last command


[{'s': 200, 'i': 4, 'd': [{'id': 000, 'name': 'login_name', 'timestamp': '2022-02-10T21:19:30.772Z', 'userType': 'Trader', 'email': '***@***.com', 'status': 'Active', 'creationTimestamp': '2022-02-10T21:18:25.053Z', 'professional': False, 'twoFactorAuth': True}]}]

API – last command


[{'s': 200, 'i': 5, 'd': [{'id': 000, 'name': 'name_in_tradovate', 'userId': 000, 'accountType': 'Customer', 'active': True, 'clearingHouseId': 0, 'riskCategoryId': 0, 'autoLiqProfileId': 0, 'marginAccountType': 'Speculator', 'legalStatus': 'Individual', 'archived': False, 'timestamp': '2022-02-11T16:14:14Z', 'nickname': 'nickname_in_tradovate'}]}]

Using the demo urls for websocket and api.

API – last command


[{'s': 200, 'i': 3, 'd': {'orderId': 3556122208}}]

API -- entity -- ORDER REJECTED -- order_status [ExecutionRejected] order_id [-1.0] last_order_id [0]
[{'e': 'props', 'd': {'entityType': 'commandReport', 'eventType': 'Created', 'entity': {'id': 3556122210, 'commandId': 3556122208, 'timestamp': '2022-03-19T06:51:42.554Z', 'commandStatus': 'ExecutionRejected', 'rejectReason': 'SessionClosed', 'text': 'Cannot place order outside of market hours. Please check contract specifications or quote info to view market hours.'}}}]

API – last command


[{'s': 200, 'i': 5, 'd': [{'id': 000, 'name': 'login_name', 'timestamp': '2022-02-10T21:19:30.772Z', 'userType': 'Trader', 'email': '****@***.com', 'status': 'Active', 'creationTimestamp': '2022-02-10T21:18:25.053Z', 'professional': False, 'twoFactorAuth': True}]}]

API – last command


[{'s': 200, 'i': 4, 'd': [{'id': 000, 'name': 'DEMO000', 'userId': 000, 'accountType': 'Customer', 'active': True, 'clearingHouseId': 0, 'riskCategoryId': 0, 'autoLiqProfileId': 0, 'marginAccountType': 'Speculator', 'legalStatus': 'Individual', 'archived': False, 'timestamp': '2022-02-10T21:19:31Z'}]}]

What is going on?

Can you expand on the above statement?

I get the same problem with device id, Does you soft the device id for place order api OK ?
I review the topic ,It is that mean a device Id I just need kepp it unic,for example use MD5(“name” + “password” + “apiKey”),and need approved this device id for use?

Apologies for the delay: I can send OSO order in demo ,but Access denied in Replay - #11 by beebee

I wouldn’t necessarily put my password in that calculation. Did you get an email from Tradovate after you created the app?

thank you very much! @beebee .
so I just need keep the device Id unique is ok? I thought the server may be check the device Id with a rule of generate to check it valid, like Md5(name + password + key)

and I try use a new App name and deviceId (keep generate it unique),I can see the new device I my client trust device list,but I has no recevie Email about the device to aprove. and has access denied in replay model.