Tradovate API 'Access Is Denied' on Place Order
Your credentials work, your token is valid, and /order/placeOrder still rejects every fill with Access is denied. It's almost never a broken key, it's one of four boring, fixable mistakes.
You wire a bot up to the Tradovate REST API, the first live order goes out, and back comes Access is denied. It feels like a wall. Your credentials worked. Authentication handed you a token. You can even see your positions. Yet /order/placeOrder rejects every fill with that same terse string, sometimes as a 401, sometimes as an HTTP 200 carrying {"failureReason":"UnknownReason","failureText":"Access is denied"}. Here's the good news: this is almost never a “your key is broken” problem. It's one of four boring, fixable mistakes, the wrong account id, an under-scoped Orders permission, the wrong demo/live host, or a mishandled isAutomated flag. Below you'll find the full checklist, what the error actually means, every root cause with a step-by-step fix, and how PickMyTrade sidesteps this whole class of problem so your TradingView alerts route to Tradovate without you debugging JSON at the open.
Quick Checklist for "Access Is Denied"
- Wrong account id, read the numeric
idfrom/account/list, never the display name (like “DEMO1235”). - Orders permission too low, the API key's Orders entitlement has to be set to Full Access, not read-only.
- Host mismatch, mint the token and send the order on the same host:
demo.tradovateapi.comfor sim,live.tradovateapi.comfor live. -
isAutomatedwrong, include the flag and set ittruefor any bot or algorithmic order, and watch the data type when you form-encode. - Stale or wrong symbol, use the active front-month contract (or its contract id), not an expired or continuous symbol.
- Live device not approved, live strictly enforces a known, stable
deviceId; demo is lenient about it.
What "Access Is Denied" Means
Access is denied is Tradovate's generic “this request isn't authorized to do that” response. The trap is that it's not an authentication error in the usual sense, you can hold a perfectly valid, unexpired access token and still get it. It fires at the point where Tradovate checks whether this token is allowed to place this order on this account. Any break in that chain, an account id the token doesn't own, an API key that was never granted order-placement rights, or a token minted against the demo host and then replayed against live, surfaces as the same three words.
Two response shapes matter here. A raw 401 Access Denied usually points at the token or the host: the token's expired, it was requested from the wrong base URL, or (on live) the device isn't approved. A 200 OK whose body reads {"failureReason":"UnknownReason","failureText":"Access is denied"} means the request authenticated fine but the order itself got refused at the entitlement layer, classically a wrong accountId or a missing Orders permission.
Because both variants print the identical string, it's easy to burn an hour re-checking passwords when the real fault is one field in the request body. Read the HTTP status first, then work the causes below in order.
Top Causes of "Access Is Denied" on Place Order
1. You're Sending the Display Name Instead of the Numeric Account Id
This is the single most common cause. Your account shows a name like DEMO1235 or TRAD123456, and it's tempting to pass the trailing number as accountId. But accountId is Tradovate's internal entity id, a separate numeric value that has nothing to do with the digits in the display name. Pass the wrong one and the token doesn't “own” that account, so the order is denied.
The fix is to read both identifiers from /account/list. Each account object returns an id (the numeric entity id you put in accountId) and a name (the human-readable label you put in accountSpec). Put simply: the account id is not the number your account happens to be named after. Never hard-code it, always fetch it.

2. The API Key's Orders Permission Isn't Full Access
Tradovate lets you scope an API key per capability. A key can read positions, read account info, and read the contract library while still having Orders set to something less than Full Access, which is exactly enough to authenticate, list accounts, and look healthy, yet be blocked the instant it tries to place an order. The fix is direct: grant the key Orders → Full Access in your Tradovate application/API settings, then re-issue the token so the new entitlement is baked in.

3. You Minted the Token on One Host and Are Ordering on Another
Tradovate runs two fully separate environments with two base URLs:
- Demo / simulation:
https://demo.tradovateapi.com/v1 - Live:
https://live.tradovateapi.com/v1
A token requested from the demo host is only valid against the demo host. Point that token at the live /order/placeOrder (or the other way around) and you get Access is denied. The classic slip is calling auth/accessTokenRequest on a malformed or mismatched base URL, then wondering why every subsequent order fails. Confirm your auth call and your order call share the same host string, character for character, and that the host matches the account you actually intend to trade.
4. isAutomated Is Missing or the Wrong Type
If a bot, algorithm, or any impersonal process triggers the order, CME rules require isAutomated: true; a human clicking a UI button is false. Beyond simply including the flag, watch the data type. When you POST JSON, isAutomated is a boolean (true). But if you form-encode the body (data= instead of json= in Python's requests), everything serializes to strings, so it has to go over the wire as the string "true". A boolean that silently drops to False, or a type the server won't parse, lands you right back at Access is denied.
5. Symbol Format, and (On Live) an Unapproved Device
Two lower-frequency causes round out the list. First, symbol format: passing an expired or malformed contract, an old expiration code, or a continuous/rollover symbol the API can't route, can read as a denial rather than a clean “bad symbol” error. Switching to the active front-month contract (or its contract id) clears it. Second, device id on live: live strictly enforces that orders come from a known, approved deviceId supplied at auth/accessTokenRequest, while demo largely ignores it. If your code places orders in demo but is denied only in live, that's the tell.
How to Fix "Access Is Denied": Step-by-Step
Fixing the account id
Authenticate and grab your access token from the correct host. Call GET /account/list on that same host. In the response, locate your account object. Copy the id value, that numeric entity id is your accountId. Copy the name value (typically your Tradovate username or the account label), that's your accountSpec. Put both into the order body. Don't reuse the digits from the display name.
Granting Orders Full Access, then re-issuing the token
Open your Tradovate application / API key settings where the per-capability permissions are listed. Set the Orders permission to Full Access (leave read scopes as needed). Save, then request a fresh access token, permission changes only take effect on tokens issued afterward. Sanity-check with a read call like GET /position/list before you retry the order.
Matching the demo/live host
Decide which environment the account lives in (sim vs live). Use demo.tradovateapi.com/v1 for sim and live.tradovateapi.com/v1 for live, for both the auth call and the order call. If you switch environments, re-authenticate; never carry a demo token to live. Remember that tokens are short-lived (roughly 90 minutes). If a previously working script starts failing mid-session, renew the token rather than re-diagnosing permissions.
Passing isAutomated correctly
Always include isAutomated in the order body. Set it true for bot/algorithmic orders, false only for genuine human-triggered UI actions. If you send JSON, keep it a boolean. If you form-encode, send the string "true". Re-send the order and confirm the response is a real order id, not a failureText.

Troubleshooting Table
| Error | Meaning | Fix |
|---|---|---|
| 401 Access Denied | Token invalid, expired, or minted on the wrong host | Re-auth on the matching demo/live host; renew before the ~90-min expiry |
| 200 + {"failureText":"Access is denied"} | Authenticated, but the order was refused at the entitlement layer | Use the numeric accountId from /account/list and grant Orders Full Access |
| Access is denied after granting Orders permission | accountId is the display-name number, not the entity id | Read the id field from /account/list |
| Works in demo, denied in live | Live device id not approved | Send a stable deviceId at auth and approve the device |
| Access is denied with a correct account | isAutomated missing or wrong data type | Include isAutomated; true for bots; correct type for JSON vs form-encoded |
| Denied on a valid-looking symbol | Expired, continuous, or malformed contract | Use the active front-month contract or its contract id |
Where PickMyTrade Fits
Most “Access is denied” tickets come from hand-rolling the auth-and-order plumbing. PickMyTrade removes that surface area entirely by brokering the Tradovate connection for you:
- Managed Auth & Host Routing, the correct demo/live host and a fresh, unexpired token are handled automatically, so a stale session never masquerades as a permission error.
- Account & Entitlement Resolution, the right numeric account id and Orders entitlement are resolved from your linked account, not guessed from a display name.
- Compliant isAutomated Handling, automated orders are flagged correctly for exchange rules every time, with no boolean-vs-string foot-guns.
- Symbol & Contract Validation, alerts map to a valid, active contract before an order is ever sent, cutting the “denied on a bad symbol” class of failure.
Trade Rejection-Free
Start your free 5-day trial, link your alerts today and trade rejection-free.
Start Your Free 5-Day TrialFrequently Asked Questions
The permission is only half the check. The most common remaining cause is a wrong accountId, you're sending the display-name number instead of the numeric entity id from /account/list. A host mismatch, like using a demo token against live, produces the same string.
Call GET /account/list on the same host you authenticated against. Use the object's id field (the numeric entity id) for accountId, and its name field for accountSpec. Never derive the id from the account's display name.
accountSpec is the human-readable string name (usually your username or account label). accountId is the numeric internal id. Both are required in the order body and both come from /account/list.
No. It's a generic denial. Check Orders → Full Access first, but if that's already set, move on to the account id, the host, and the isAutomated type before assuming the key itself is broken.
Live strictly enforces an approved deviceId and its own host, while demo is lenient. Supply a stable device id at authentication and approve the device to clear it.
true for any order placed by a bot, script, or algorithm (an exchange requirement); false only when a human physically triggers it through a UI.
Yes. Tradovate sometimes returns 200 OK with {"failureReason":"UnknownReason","failureText":"Access is denied"} in the body. Always inspect the body, not just the status code.
It can. Some prop firms restrict or forbid direct API automation on evaluation accounts, and data/entitlement state varies by firm and account size, check your firm's current rules before automating.
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. 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 Tradovate platform and documentation before acting.