Rate limits are not a thing that happens to careless developers. They are a budget, they are shared more widely than most traders realise, and the way an automated account spends them is nothing like an average. You do not drift into a limit at a steady rate. You sit well under it all morning and then spend a third of it in the ninety seconds after a signal fires.
This is what Tradovate publishes, what actually consumes the budget, what the platform does when you cross a line, and the handful of choices on your side that decide whether you ever see one.
1. What Tradovate actually publishes
Start with the source rather than with a forum post. The rate-limit page in the API documentation sets out two separate systems, and confusing them is the reason so many third-hand summaries contradict each other. There is a limit on you as an authenticated user, and there are limits on individual endpoints. They fail differently.
| Caller | Per hour | Per minute | Per second |
|---|---|---|---|
| Anonymous | 1,000 | 1,000 | 100 |
| Authenticated | 5,000 | 5,000 | 5,000 |
Cross the user-level limit and you get a 429 Too Many Requests with an empty response body, and the documented recovery is to wait an hour before sending another request. The same page notes that this one cannot be resolved programmatically from a third-party application. There is no clever backoff that shortens it. An hour is an hour, and if it starts at 09:35 it ends at 10:35 whether or not the market cooperates.
Endpoint limits behave completely differently
Specific endpoints carry their own hourly ceilings with their own back-off periods, and the ones worth knowing are the authentication endpoints, because they are the ones an automated setup hits unintentionally. Requesting an access token is capped in the single digits per hour on failed requests, and renewing one is capped in the low tens per hour counting every request, successful or not. A reconnect loop that re-authenticates on every attempt will find that ceiling long before it finds the user-level one.
Cross an endpoint limit and the response is stranger and more useful: a 200 OK carrying penalty fields rather than an error. The official handling guidance describes it plainly: the response carries p-time, which is the time in seconds before you can make the request again, and p-ticket, which you must include as an additional field in the body when you retry, alongside the original fields. A third field, p-captcha, means a third-party application cannot complete the request at all and the user has to go and do it themselves.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
(empty body)HTTP/1.1 200 OK
{
"p-ticket": "aGVsbG8gdGhlcmU",
"p-time": 30,
"p-captcha": false
}2. Where an automated account's budget actually goes
Traders tend to estimate their usage by counting trades. That number is almost never the one that matters, because placing the trade is a small share of the traffic an automated setup generates.
- Placing a bracket is not one request. Resolving the instrument to a tradeable contract, placing the entry with its protective legs attached, and confirming what came back are separate calls.
- Reading is where the volume lives. Positions, working orders, account balances and fills all have to come from somewhere, and a setup that polls them on a timer spends far more than one that listens to a streaming feed.
- Session management costs requests. Tokens expire and have to be renewed, and every renewal counts against an endpoint limit with a low ceiling.
- Reconnects are the silent multiplier. A feed that drops and redials repeatedly can spend more in a minute than a morning of trading.
- Anything else signed in as you shares the budget. A second tool on the same credentials, a dashboard left open, a script you forgot about.
The limit that catches people is almost never the hourly one. It is the burst.
Think about what happens at a session open. A strategy fires across several accounts at once. Each one wants a contract lookup and an order placement. Each fill produces position and order updates that something has to read. If your setup answers a burst by polling harder, which is the intuitive thing to do when you want to know what just happened, then the moment you most need the API is the moment you are most likely to be refused by it.
The shared-quota trap
One more structural thing worth understanding, because it explains behaviour that otherwise looks random. Limits attach to identities, and an identity can be shared more widely than the trader sitting in front of it. If you run two automation tools against the same broker login, they are not politely dividing a budget between them. They are both spending from it, neither knows the other exists, and the one that gets refused is whichever happened to be second. The fix is not tuning. It is not doing that.
3. What actually happens when you hit one
The mechanical answer is above. The answer that matters is what it does to your trading, and that depends entirely on what the software in the middle does with the refusal.
A 429 is not the end of the world if it is handled. It usually arrives with a Retry-After header, and the correct response is to wait exactly that long rather than to guess, then try again. Our client honours the header when it is present and falls back to an exponential wait when it is not, and it keeps the failure attributed to the connection it happened on so that one busy account does not slow down everybody else on the platform.
A lockout is worse and needs a different reflex. Once a caller has been told to wait an hour, the only thing that makes it worse is continuing to send. So the correct behaviour is to stop, immediately and completely, for everything sharing that connection, and to surface it rather than absorb it. Continuing to poll through a lockout is how a fifteen-minute problem becomes a session-long one.
Reads and writes compete, so reads have to lose
Everything above shares one budget, which means the position refresh you do not urgently need is competing with the stop-loss you very much do. When pressure rises, something has to give way, and it cannot be the order. Our pipeline treats order traffic as the priority class and sheds background reads first: a stale balance reading is an inconvenience, a refused order is a position you did not get.
This is also why caching is not a performance optimisation on this API. It is a safety measure. Every read you can answer from something you already know is a request you did not spend on a budget your orders need.
What a refused order does on our side
When an order genuinely cannot be sent because the budget is exhausted, the important property is that it fails loudly and recoverably. The order is recorded as failed with the reason attached, it is classified as an infrastructure failure rather than a broker rejection, and because that class is retryable it can be retried from the dashboard without re-firing the alert. What it never does is quietly disappear, and it never turns into a second order later because something retried it in the background after the state of the market had moved on.
4. Staying under the line
Most of the levers that matter are yours, not your vendor's. In rough order of how much they buy you:
- Stop polling for things that are pushed to you. A streaming feed tells you about fills, cancels and position changes as they happen. Polling the same facts on a timer spends budget to learn them slightly later.
- Fix duplicate alerts before you fix anything else. Every duplicate is a full order's worth of traffic and a real risk of a real position.
- Do not run two automation tools on one broker login. They share the budget and neither can see the other.
- Make reconnects back off. A redial loop hits the authentication ceilings fastest of anything on this list, and those have the lowest limits.
- Watch the bursts, not the averages. If your worst minute is fine, your hour is fine. The reverse is not true.
- Keep one login per purpose. A separate read-only dashboard session is easier to reason about than one credential doing everything.
If you are running a copier across several accounts, the arithmetic changes shape, and it is worth being deliberate about it. Fanning one signal out to several accounts multiplies the order traffic by the number of accounts, so the thing that keeps it safe is that the fan-out is the only multiplication happening: one contract lookup reused rather than repeated, one streaming feed per connection rather than per account, and reads that are shared instead of duplicated per follower.
Higher limits can be requested from the broker once you can demonstrate that you handle the existing ones properly, which is a reasonable position for them to take. It is also worth noting what "properly" means to them: honouring back-off periods, not retrying into a penalty, and not treating a penalty response as a success. If your integration does those three things you are in better shape than most of what they see.
The related reading here is the duplicate-alert problem, since duplicates are the cheapest traffic to eliminate and the most expensive to ignore. That is covered in the guide on webhooks firing twice.
Checklist
- Your integration parses the response body, not just the status code, so a penalty response is never read as a success.
- A 429 waits for the Retry-After value rather than a guess, and backs off exponentially when the header is absent.
- A lockout stops all traffic on that connection instead of continuing to poll through it.
- Order traffic takes priority over background reads when the budget is under pressure.
- A timeout on an order placement is never retried automatically; it is resolved by reading the broker's own order list.
- Only one automation tool is signed in to each broker login.
- Reconnects back off, so a dropped feed cannot burn the authentication limits.
- An order refused for rate limits is recorded with a reason and can be retried without re-firing the alert.