Tradovate OSO 'Access Denied' in Market Replay
The identical OSO bracket that fires cleanly in demo comes back Access is denied in Market Replay. Replay authenticates differently, and the denial almost always traces to how the token reached the replay socket.
You've got an OSO bracket firing cleanly in demo. Same code, same contract, same body, you point it at Market Replay to backtest a session, and Tradovate hands you {"failureReason":"UnknownReason","failureText":"Access is denied"}. Nothing changed on your side except the environment, so it feels like a permissions bug. It isn't. Replay authenticates differently from demo and live, and the “denied” almost always traces back to one thing: the token was never presented to the replay socket correctly, or the order is aimed at an account the replay session doesn't own. Get the auth flow right and the OSO that works in demo works in Replay too. Here's exactly what's happening and how to fix it.
Short version: Replay has no auth endpoint of its own, posting to
replay.tradovateapi.com/v1/auth/accessTokenRequestjust 404s. Request the access token from thedemo(orlive) auth endpoint, openwss://replay.tradovateapi.com/v1/websocket, send anauthorizeframe with that token, then route every call, including your OSO, through that same socket against the account the replay session created.
What the Error Looks Like
The tell is that the request body is byte-for-byte identical to one that works. Fire the OSO in demo and you get back a clean set of order ids:
-
{"orderId":3696709628,"oso1Id":3696709629,"oso2Id":3696709630}
Send the same payload in Replay and instead of ids you get the denial:
-
{"failureReason":"UnknownReason","failureText":"Access is denied"}
The contract is valid for the replay window (say MNQM2 during a session that covers it), the token authenticated, and you can even pull account data. Only the order placement is refused. That pattern, everything reads fine, the write is denied, is the fingerprint of an authorization-scope problem, not a bad credential.
Why Market Replay Throws "Access Is Denied"
Two design quirks of the replay environment cause almost all of these denials.
Replay Has No HTTP Auth Endpoint
On demo and live you're used to a REST world: you POST to auth/accessTokenRequest, get a token, then POST orders to order/placeOrder over HTTPS. The replay host doesn't work that way. There is no auth/accessTokenRequest on replay.tradovateapi.com, so a POST to https://replay.tradovateapi.com/v1/auth/accessTokenRequest returns a 404 Not Found. People see that 404, assume the endpoint moved, and start guessing URLs, when the real answer is that replay never had one.

The token still comes from the normal auth endpoints. You then carry it to the replay WebSocket and authorize there. Everything after that, market data subscriptions, clock control, and order placement, travels as framed messages over that one socket. If your OSO is still going out over HTTPS while your token lives on the socket, Tradovate has no authorized session to attach the order to, and you get “Access is denied.”
Every Replay Session Builds a Fresh, Throwaway Account
Replay isn't trading your demo or live account. When you call replay/initializeClock, Tradovate starts a session and creates a brand-new replay account seeded with the initialBalance you pass. That account exists only for the session and is discarded when the session ends. Only one replay session runs per user at a time, and calling initializeClock again resets whatever was running.
Here's the catch that produces denials: your OSO has to target that replay account, the one the current session just minted, not your demo account id, not your live account id, and not a stale id from a session you already reset. Aim the order at the wrong account and Tradovate refuses it with the same terse string.
The Fix, Step by Step
Request the token from demo or live auth (never replay)
Authenticate against a real environment endpoint and keep the returned accessToken: sim/backtest work uses https://demo.tradovateapi.com/v1/auth/accessTokenRequest; live-credentialed work uses https://live.tradovateapi.com/v1/auth/accessTokenRequest. Send the full credential set your environment expects, name, password, appId, appVersion, cid, sec, and, especially on live, a stable deviceId. Do not POST anything to a replay.tradovateapi.com auth path; that's the 404 trap.
Open the replay socket and authorize with that token
Connect to wss://replay.tradovateapi.com/v1/websocket. Tradovate opens the stream with a single-character open frame, the letter o. As soon as you see it, send the authorize frame using the token from Step 1: authorize\n{id}\n\n{accessToken}. That newline-delimited shape, endpoint, request id, blank line, body, is how every replay message is framed. A successful authorize is what actually grants your session the right to place orders on the socket. Skip it, and even a perfectly valid token leaves you unauthorized for order placement.
Initialize the clock and capture the session's account
With the socket authorized, set up the replay session by sending replay/initializeClock with your window and speed: {"startTimestamp":"2019-08-26T16:43:00.000Z","speed":100,"initialBalance":51000}. speed is a percentage (roughly 0–400), and initialBalance funds the temporary replay account. Once the clock is running, resolve the account tied to this session and hold onto its numeric id. This is the account your OSO must reference in accountId / accountSpec, not the id you'd use in demo or live.
Send the OSO through the replay socket, against that account
Now place the bracket as a WebSocket frame on the same authorized socket, using the replay session's account: order/placeorder\n{id}\n\n{orderBodyJson}. Keep the OSO body the same one that works in demo, but swap in the replay account id you captured in Step 3. When the account and the authorize step line up, the response flips from failureText back to real orderId / oso1Id / oso2Id values.
Keep the token and socket alive
Access tokens are short-lived, figure on roughly 80 to 90 minutes. A long replay run can outlast the token, and when it lapses mid-session the next order reads as a denial. Send periodic heartbeat frames to keep the socket from idling out, and renew the token before it expires rather than after. Treat a sudden “Access is denied” partway through a working session as a probable expiry, not a fresh permissions bug.


Still Getting "Access Is Denied"? Work This Checklist
If the auth flow is right and the OSO still won't place, run these in order:
- Account id mismatch, confirm the order targets the replay session's account, not a demo/live id or a stale id from a session you already reset with a newer initializeClock.
- API key permissions, the key you authenticated with must have Orders granted, not set to denied. A read-only key authenticates and lists accounts but is blocked the moment it tries to place an order.
- Device id on live, if you're minting the token from the live host, supply a stable, approved deviceId. Demo is lenient about this; live is not, and the credentials differ between the two.
- Authorize frame skipped, make sure you actually sent authorize after the o open frame and got a success back before any order goes out.
- One-session limit, remember only one replay session runs per user. If another session is live (from the UI or a previous run), your new one may be resetting it out from under you.
- Contract window, the symbol has to be valid for the replay date, so use the contract that was the active front month during your startTimestamp, not today's front month.
Troubleshooting Table
| Symptom | Cause | Fix |
|---|---|---|
| 404 on the replay auth URL | Replay has no HTTP auth endpoint | Get the token from demo/live auth/accessTokenRequest, then authorize the replay socket |
| OSO returns {"failureText":"Access is denied"} | Socket never authorized, or order aimed at the wrong account | Send the authorize frame after o; target the replay session's account |
| Works in demo, denied in Replay | Order still going over HTTPS instead of the socket | Send order/placeorder as a framed message on the replay WebSocket |
| Timeout or denial right after initializeClock | Using an old account from a reset session | Re-resolve and use the account tied to the current session |
| A working session suddenly denies orders | Access token expired mid-run | Heartbeat the socket and renew the token before ~80–90 min |
| Denied only when authenticating on live | Missing/unapproved deviceId or denied Orders permission | Supply a stable approved device id; grant the key Orders access |
Where PickMyTrade Fits
Hand-wiring the demo-token-to-replay-socket dance is where most of these denials come from, one missed authorize frame or one wrong account id and the whole session refuses orders. If your goal is to run a strategy against Tradovate from TradingView alerts rather than to babysit WebSocket frames, PickMyTrade brokers the connection for you: it manages the token, targets the right account, and flags automated orders correctly so bracket entries route without you debugging JSON at the open.
Route Brackets Without Touching the API
Start your free 5-day trial, link your alerts and route brackets to Tradovate without touching the API.
Start Your Free 5-Day TrialFrequently Asked Questions
Demo and Replay are different environments with different plumbing. In Replay the token has to be minted from the demo or live auth endpoint and then presented over the replay WebSocket with an authorize frame. Skip that authorize step, or send the order against an account the session doesn't own, and the identical body that succeeds in demo comes back with failureText "Access is denied".
There is no auth endpoint on the replay host. Posting to https://replay.tradovateapi.com/v1/auth/accessTokenRequest returns 404 by design. Request the access token from https://demo.tradovateapi.com/v1/auth/accessTokenRequest or the live equivalent, then reuse that token to authorize the replay WebSocket.
Through the WebSocket. Once you're authorized on wss://replay.tradovateapi.com/v1/websocket, every call, including order/placeOrder, is sent as a framed message like order/placeorder\n{id}\n\n{body}. HTTP calls to the replay host won't route your replay orders.
replay/initializeClock starts a replay session and creates a throwaway replay account that's funded for the session and discarded when it ends. Only one replay session exists per user at a time, and a new initializeClock resets the previous one. Orders must target the account tied to the current session, a stale or demo/live account id produces access-denied or timeout errors.
Yes. Tradovate access tokens are short-lived, roughly 80 to 90 minutes. Keep the socket alive with periodic heartbeat frames and renew the token before it lapses so a mid-session expiry doesn't masquerade as a denial.
It can, especially when you authenticate against live instead of demo. Live enforces a stable, approved deviceId supplied at authentication, and the credentials differ between demo and live. If Replay works off a demo token but fails off a live one, check the device id and confirm the API key's Orders permission is granted, not denied.
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.