Tradovate API 404 on md/getChart Historical Data
You wired up a WebSocket, sent a clean md/getChart request, and got Not found: md/getChart back. Nine times out of ten it's the wrong socket or a symbol Tradovate can't resolve.
You wired up a WebSocket, authorized it, sent a clean md/getChart request for a few hundred bars of history, and the server slapped you with Not found: md/getChart. Nothing in the payload looks wrong. So why a 404 on an endpoint that clearly exists in the docs?
Nine times out of ten it comes down to one of two things: you sent the request to the wrong socket, or you asked for a symbol Tradovate can't resolve. Both throw a “not found” that looks identical from your side. Let's walk through each cause, in the order worth checking, and get your historical bars flowing.
What the 404 Is Actually Telling You
A 404 from a REST call means the URL path doesn't exist. Over Tradovate's WebSocket it's the same idea: the frame you send names an endpoint, and if the socket you're connected to doesn't serve that endpoint, you get Not found with the endpoint name echoed back. It's not saying your symbol is bad or your token expired. It's saying “I don't have a route for that here.”
That distinction matters. If the message is literally Not found: md/getChart, the endpoint itself isn't reachable on that connection, which points straight at the wrong-socket problem. If the request reaches the market-data engine but the contract can't be found, you'll see a failure tied to the symbol instead. Read the exact text before you start changing code.
Cause #1: You're Hitting the Wrong WebSocket
This is the big one, and it catches almost everyone the first time. Tradovate runs two different WebSocket services, and they are not interchangeable:
- The trading / API socket handles orders, positions, accounts, and the rest of the entity data. Live is
wss://live.tradovateapi.com/v1/websocket; demo iswss://demo.tradovateapi.com/v1/websocket. - The market-data socket handles quotes, DOM, and charts. Live is
wss://md.tradovateapi.com/v1/websocket; demo iswss://md-demo.tradovateapi.com/v1/websocket.
All the md/ endpoints, md/getChart, md/subscribeQuote, md/subscribeDOM, only exist on the market-data socket. Send them to the trading socket and there's simply no route to match, so you get Not found: md/getChart. The trading socket won't hint that you're in the wrong place; it just reports the endpoint as missing.

The fix: open a second WebSocket connection to the market-data host and send your chart request there. A common mistake is authorizing the trading socket beautifully, then reusing that same connection for market data. You need both sockets open, and each one needs its own authorize frame with your access token before it will answer any request.
One more trap on the same theme, match your environment. If your access token came from the live auth endpoint, use md.tradovateapi.com. If it came from demo, use md-demo.tradovateapi.com. Crossing a live token with the demo market-data host (or vice versa) gets you rejected before the chart request even matters. Hostnames do get updated occasionally, so confirm the current ones against Tradovate's own developer docs rather than trusting a snippet you copied a year ago.
Cause #2: The Symbol Isn't a Real Contract
Say your socket is correct and you're still getting a not-found response tied to the request. Look hard at the symbol. Chart requests need a fully qualified futures contract, not the product root you see in a watchlist.
“YM” isn't tradable on its own, it's the root. What Tradovate can resolve is the specific contract, like YMH5, where H is the March month code and 5 is the year 2025. Pass the bare root, a wrong month code, or a contract that's already expired and rolled to the next quarter, and the engine can't find anything to build a chart from. Here's the standard futures month-code map:
| Month | Code | Month | Code |
|---|---|---|---|
| January | F | July | N |
| February | G | August | Q |
| March | H | September | U |
| April | J | October | V |
| May | K | November | X |
| June | M | December | Z |

Rather than hard-coding a symbol string, resolve the current front-month contract programmatically. The contract/find and product lookup endpoints on the trading socket give you the exact tradable symbol, so you're never guessing whether ES rolled from December to March. If you must use a continuous or “front month” alias, double-check it's one Tradovate actually accepts for charts, a lot of continuous notations that work in a charting UI won't resolve in a raw md/getChart call. When in doubt, request the explicit contract.
This is also the classic reason a request that worked last quarter suddenly 404s: the contract you pinned expired. Rolls happen on a fixed schedule, and an expired symbol is gone from the data engine. Automate the roll instead of chasing it manually.
Cause #3: Your Market-Data Entitlement Isn't Set Up
Correct socket, valid contract, and still nothing useful? Now check what your account is actually entitled to. Two layers matter here.
First, API access itself has to be switched on. That's the API Access add-on inside your Tradovate account settings, which enables programmatic access and lets you generate the credentials your auth flow needs. Without it, your token won't carry the permissions the market-data engine expects.

Second, real-time market data over the API needs the relevant exchange agreement and license. For CME products that means a monthly market-data license on top of your plan. The exact amount is set by the exchange and changes over time, so confirm the current figure with Tradovate rather than trusting a number you read somewhere, it's typically a recurring monthly charge, not trivial. Without that entitlement, a well-formed chart request can still come back inaccessible or empty, because you're asking for data your account isn't licensed to receive. If your symbols show as inaccessible rather than not-found, that's the flavor of problem you're looking at.
If you're on a prop-firm or evaluation account, you often can't self-subscribe to that data at all, the firm controls it. In that case the entitlement fix runs through your firm's support, not Tradovate's.
Cause #4: A Malformed Frame or an Unauthorized Socket
Tradovate's WebSocket doesn't speak plain JSON. Each request is a text frame with a specific shape: the endpoint, then the request id, then an (often empty) query line, then the JSON body, separated by newlines, for example md/getChart\n2\n\n{ ... }. Get that structure wrong and the server may not parse the endpoint name correctly, which can surface as a not-found style error even though your intent was fine.
- Authorize first. The very first frame on the market-data socket must be
authorize\n1\n\n<yourAccessToken>. Fire offmd/getChartbefore the socket is authorized and it won't be honored. - Keep the socket alive. The connection expects a periodic heartbeat frame. Miss it and the socket drops; requests sent to a half-dead connection fail in confusing ways.
Build the frame exactly as documented, authorize before you request, and send heartbeats on schedule. Most “random” 404s on a socket that was working an hour ago trace back to one of these three.
A Quick Checklist to Clear the 404
| Check | What to confirm |
|---|---|
| Right socket | md/getChart goes to md.tradovateapi.com (live) or md-demo.tradovateapi.com (demo), not the trading socket. |
| Matching environment | Live token → live market-data host; demo token → demo host. |
| Full contract symbol | Use ESH5-style symbols, not the ES root, and not an expired contract. |
| Entitlements on | API Access add-on enabled and the exchange market-data license active. |
| Frame + auth | Authorize the socket first, then send a correctly newline-delimited request frame. |
Once the Chart Data Starts Flowing
When the request lands, the response hands you back subscription ids, a historical id and a real-time id, and streams bars. History arrives in batches, and each batch ends with an “end of history” marker so you know when a chunk is complete. There's no fixed hard cap on how much you can pull per request, but the practical limit shifts day to day, so a big ask (say, minute bars over several years) comes back truncated instead of erroring out.
To page further back, grab the oldest timestamp you received, fire a new md/getChart with that as your closest timestamp and your target date as the far timestamp, and repeat until you've got the range you need. And when you're done with a real-time chart, cancel it with md/cancelChart using the real-time id so you're not holding a subscription you no longer read.
Where PickMyTrade Fits
Most people hitting this 404 don't actually want to become WebSocket plumbers, they want a strategy to execute on Tradovate without babysitting sockets, tokens, and frame formats. PickMyTrade automates order routing from TradingView alerts to Tradovate, so you don't have to wire raw WebSocket frames yourself just to execute a strategy.
- No raw socket plumbing your TradingView alerts route to Tradovate without hand-coding a single WebSocket frame.
- Managed auth tokens and hosts are handled for you, so a wrong-environment mismatch never becomes your problem.
- Symbol handling contract resolution is managed under the hood instead of you hand-tracking month codes and rolls.
- Rate-limit-safe routing spaces order flow so nothing bounces off Tradovate's request limits.
Skip the Raw Socket Wiring
Want your TradingView strategy to trade Tradovate without hand-coding a single WebSocket frame? See how PickMyTrade automates the whole order flow.
Start Your Free 5-Day TrialFrequently Asked Questions
Because the endpoint doesn't exist on the socket you sent it to. Market-data endpoints only live on the dedicated market-data WebSocket. Send md/getChart over the trading/API socket and the server has no route for it, so it answers Not found: md/getChart. Move the request to the market-data host and it resolves.
Live market data uses wss://md.tradovateapi.com/v1/websocket and demo uses wss://md-demo.tradovateapi.com/v1/websocket. Those are separate from the trading sockets. Confirm the current hostnames in Tradovate's developer docs before you commit them to code.
Chart requests need a full contract symbol, not the root. YM can't be resolved, but YMH5 (March 2025) can. A root symbol, a wrong month code, or an already-expired contract all come back not found. Pull the exact tradable symbol from a contract lookup first.
Real-time API market data requires the relevant exchange agreement and license, which for CME products is a recurring monthly fee. The amount varies and is set by the exchange, so confirm the current rate with Tradovate. Without it, symbols come back inaccessible even when your request is perfectly formed.
PickMyTrade automates order routing from TradingView alerts to Tradovate, so you don't have to wire raw WebSocket frames just to execute a strategy. It isn't a bulk historical-data feed, but for automation it removes most of the low-level plumbing that causes errors like this one.
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.