Tradovate OAuth 'invalid_client' Token Error
Your token exchange returns invalid_client instead of an access token. Here's why the three app-identity fields don't line up, and how to fix each one.
You've got a fresh authorization code in hand, you fire off the token exchange, and instead of an access token you get slapped with [invalid_client] client_id, redirect_uri and client_secret do not match existing setup. It shows up as invalid_client, sometimes wrapped in a 400 or 401, and it stops your Tradovate integration cold before a single order can go out.
Here's the reassuring part: this error almost never means something is broken on Tradovate's end. It means the three values that identify your app during the token exchange don't line up with what you registered. Fix the mismatch and the same request that's failing right now goes straight through.
Below is every reason that mismatch happens, in the order you're most likely to hit it, with the exact spots to check in your OAuth app and your code.
Quick Checklist for the invalid_client Error
- Wrong client_id, copy it again from your registered OAuth app; watch for a leading space or a trailing newline.
- Wrong client_secret, if you ever regenerated it, the old value is dead; paste the current one everywhere.
- redirect_uri not identical, it must match the registered value byte for byte in both the authorize step and the token exchange.
- Live vs demo mix-up, send the token request to the same environment your app was registered in.
- Stale example code, old sample repos ship outdated endpoint URLs; verify yours against the current official docs.
- Credentials in the wrong place, make sure all of grant_type, code, client_id, client_secret and redirect_uri are actually in the request body.
What "[invalid_client] client_id, redirect_uri and client_secret do not match existing setup" Means
OAuth splits your integration into two separate identities. One is you, the account holder, proven by your username and password. The other is your app, proven by a client_id and a client_secret. The invalid_client error is entirely about the second one. It's the standard OAuth 2.0 response for “client authentication failed,” and Tradovate spells out exactly which fields it checked: client_id, redirect_uri, and client_secret.
So when you see this, the authorization code you just received is usually fine. The server got to the point of confirming who your app is, compared the three values you sent against the record it saved when you registered, found a difference, and refused to go further. It won't tell you which of the three is off. That's the annoying part, and it's why the fix is a process of elimination.
The request that throws it is your token exchange: a POST to the OAuth token endpoint on your environment's host (live.tradovateapi.com for live, demo.tradovateapi.com for demo) carrying grant_type=authorization_code, the code, and the three identity fields. A success hands back an access_token and an expires_in. A failure hands back error and error_description, and here, that error is invalid_client.
Top Causes of the invalid_client Error
1. The client_id doesn't match your registered app
This is the simplest cause and the easiest to overlook. The client_id in your token request has to be the one Tradovate assigned when you registered the OAuth app. People trip on it in small ways: pasting the app's name instead of its id, grabbing an id from a different app, or dragging along an invisible space or newline when copying. Whitespace is the sneakiest, because the value looks right in your editor. Re-copy the id straight from the registration screen and trim it.

2. The client_secret is wrong or was regenerated
The client_secret is the password half of your app's identity, and it has to match exactly. The classic trap is regeneration: you hit “regenerate secret” at some point to rotate it, the platform issued a new one, and the old value is now permanently invalid. If your code, your .env file, or your deployment config still holds the previous secret, every single exchange returns invalid_client. The same thing happens if you never actually generated a secret and are sending an empty or placeholder value.
3. The redirect_uri isn't a byte-for-byte match
This is the cause that burns the most hours, because the URIs look the same at a glance. The redirect_uri has to be identical in three places: the value saved on your registered app, the value in your authorize request, and the value in your token exchange. Identical means character for character. These all count as different:
- A trailing slash on one but not the other (
/callbackvs/callback/). -
httpin one place,httpsin another. - A port number present here, missing there (
localhost:3030vslocalhost). - Different capitalization anywhere in the path.
-
localhostin one spot and127.0.0.1in another.
Any one of those makes Tradovate treat it as an unregistered redirect and fold it into the invalid_client response.

4. You registered on one environment and are calling the other
An OAuth app is tied to a single environment. Register it on demo and its client_id and client_secret only exist on the demo side. If your authorize URL or your token exchange points at the live host while the app lives on demo, or the reverse, the credentials simply aren't found there, and you get invalid_client. Confirm which environment you registered in, then make sure both the authorization step and the token endpoint target that same environment.
5. You're running stale example code
OAuth samples drift. An older sample project can carry endpoint URLs, a hard-coded redirect_uri, or a request shape that no longer lines up with a freshly registered app. If you cloned a tutorial and it “just doesn't work,” don't assume your credentials are wrong, check that the endpoints and the request body in that code still match the current official Tradovate documentation before you spend an hour debugging your secret.
6. The credentials are missing or in the wrong part of the request
Every field the server checks has to actually arrive, in the right place, in the right format. Your token POST needs grant_type=authorization_code, the code, client_id, client_secret and redirect_uri, sent in the body the way the current docs specify. Drop one of the three identity fields, or send the body in a format the endpoint doesn't expect, and the app can't be authenticated, which surfaces as invalid_client.
How to Fix the invalid_client Error: Step-by-Step
Re-copy the client_id and client_secret
Open your OAuth app's registration screen (under the API Access area of your settings). Copy the client_id fresh and paste it into your config, then delete any stray leading or trailing whitespace. Do the same for the client_secret; if you're not certain the current one matches your code, regenerate it, update every place that stores it, and redeploy.
Note the registered redirect_uri
Record the exact redirect_uri saved on the app so you can compare it against your code next.
Lock the redirect_uri to a single constant
Define one REDIRECT_URI constant per environment in your code. Use that exact constant to build the authorize URL and the token exchange body, never retype it. Diff it against the registered value, watching for slashes, scheme, port, and case, and update whichever side is wrong so all three copies are identical.
Confirm the environment and endpoints
Decide which environment the app is registered in, live or demo. Point the authorize request at https://trader.tradovate.com/oauth with response_type=code, your client_id, and the matching redirect_uri. Send the token exchange to the OAuth token endpoint on the same environment's host, live.tradovateapi.com or demo.tradovateapi.com, using the exact path in the current API reference.
Re-run the flow end to end
Request a brand-new authorization code, then exchange it immediately. Codes are single-use, so don't reuse the one that already failed.

Troubleshooting Table
| Error | Meaning | Fix |
|---|---|---|
[invalid_client] client_id, redirect_uri and client_secret do not match existing setup | One or more of the three app-identity fields don't match the registered OAuth app. | Re-copy client_id and client_secret; make redirect_uri identical in all three places. |
invalid_client (no detail) | Client authentication failed, the app couldn't be identified. | Confirm client_id/secret are correct and sent in the request body, not left blank. |
invalid_grant | The authorization code is expired, already used, or tied to a different redirect_uri. | Request a fresh code and exchange it immediately with the matching redirect_uri. |
redirect_uri_mismatch | The redirect_uri doesn't match the one on the app. | Align scheme, host, port, path and trailing slash byte for byte. |
unsupported_grant_type | The grant_type value is missing or misspelled. | Send grant_type=authorization_code exactly. |
| Credentials valid on demo, failing on live | App registered in one environment, request sent to the other. | Target the authorize URL and token endpoint at the app's own environment. |
Prevent This with PickMyTrade
The whole OAuth handshake exists so software can trade your account for you. If that software is PickMyTrade, you never touch a client_secret or debug a redirect_uri at all, you connect your Tradovate account once and route your TradingView alerts to live orders from there.
- Guided account connection, link Tradovate through a walked-through flow instead of hand-rolling a token exchange.
- Managed sessions, the token lifecycle and renewals run behind the scenes, so there's no auth code for you to keep alive.
- Environment-aware routing, demo and live stay separated so credentials never cross wires.
- Multi-account sync, mirror the same alert across several accounts without wiring up OAuth for each one.
Skip the OAuth Handshake
Connect Tradovate to PickMyTrade once and route your TradingView alerts to live orders, no client_secret or redirect_uri to debug.
Start Your Free 5-Day TrialFrequently Asked Questions
It means Tradovate couldn't authenticate your application during the token exchange. The full message, [invalid_client] client_id, redirect_uri and client_secret do not match existing setup, is telling you that at least one of those three values in your POST doesn't match the record stored for your registered OAuth app. The authorization code was fine; the app identity is what failed.
Your web login uses your username and password. The OAuth token exchange uses a separate set of credentials, client_id and client_secret, that belong to the app you registered, not to your trading account. A perfect web login says nothing about whether those app credentials are right, so the two pass and fail independently.
Byte for byte. The redirect_uri in the authorize request and the one in the token exchange both have to match the value saved on the app, character for character. A trailing slash, http vs https, a different port, or a change in capitalization all count as a mismatch. Define one constant and reuse it everywhere.
Yes. An OAuth app is registered against one environment. If you registered on demo but post the token request to the live host, or the reverse, the credentials aren't found and you get invalid_client. Point the authorize URL and the token endpoint at the same environment your app was created in.
Regenerating a secret instantly kills the old one. If your code, environment file, or deployment still sends the previous value, every exchange fails. Copy the new secret into every place that holds it, redeploy, and clear any cached config so nothing keeps handing over the retired string.
No, and the difference points you to the real problem. invalid_client is about app identity, a wrong client_id, wrong client_secret, or mismatched redirect_uri. invalid_grant is about the authorization code, it expired, it was already used, or it was issued for a different redirect_uri. If you see invalid_grant, request a fresh code instead of re-checking your secret.
OAuth with client_id and client_secret is one route, and it's the one that throws invalid_client. Tradovate also supports a direct access-token request using an app id and API secret. If you're automating your own account and don't need to send other users through a consent screen, the direct token request is often simpler and skips the OAuth handshake entirely.
Open your account's application or settings area, go to the API Access section, and find the OAuth registration you created. The client_id and client_secret live there, along with the redirect_uri you saved. Labels shift over time, so if you don't see an API Access tab, check the current settings layout or the official documentation for the equivalent screen.
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 platform documentation before acting.