Exchange Short Grant Code

View as Markdown
### Redeem a short grant code for a session. **Available to:** Anonymous callers, from a NinjaTrader-owned origin **Environments:** Live. The endpoint is not blocked on Demo, but it only ever redeems codes minted in partner mode, which depends on a partner configuration provisioned on Live. **[Rate Limit](/overview/core-concepts/rate-limits):** 10 requests per hour, 3-second back-off, counts failed requests only **Partners do not normally call this endpoint.** It is the other half of the [`shortGrantCode`](/api/rest-api-endpoints/authentication/short-grant-code) handoff, and it is called by the NinjaTrader-hosted application that receives your redirect: the application reads the code from the URL, exchanges it here for a real access token, and the user lands signed in. It is documented so you can reason about the full round trip and interpret what a user sees when a handoff fails. <Warning>**Warning:** This endpoint returns **HTTP 200** for successes **and** business errors. Test for success by checking both `errorText` and whether `accessToken` is present: a restricted session returns an access token alongside a non-empty `errorText` carrying a warning, so `errorText` alone misreads that case as a failure.</Warning> **How the Code Is Validated** Four checks run in order, and the first failure ends the request: 1. **Origin.** The request must come from a NinjaTrader-owned origin: a `tradovate.com`, `ninjatrader.com`, or `ninjatrader.dev` host. This is why the exchange is performed by the NinjaTrader application rather than by your own page. 2. **The code itself.** Codes are single-use: redemption deletes the stored code, so a second attempt with the same value fails. Codes also expire on the lifetime returned as `expires_in` when they were minted, and a newer code for the same user supersedes an older one. 3. **Client IP.** The resolved client IP must match the `expectedClientIp` the code was bound to. 4. **The user.** The account is re-validated at redemption: it must be an active trader account that is not an organization administrator, and it must still belong to the partner organization the code was minted for. All four failure modes surface as `errorText` values on an `HTTP 200` response. See the table below. <Warning>**Warning:** A code that reaches step 2 is consumed even when the exchange then fails. Retrying the same code after an IP or account failure returns `"Invalid code"` rather than the original reason, which hides the real cause. Mint a fresh code for every attempt.</Warning> Because the IP comparison is an exact string match, two textual forms of the same IPv6 address do not match. Emit `expectedClientIp` in the same form the client will present at redemption. **Field Details** `code` is the value returned by [`shortGrantCode`](/api/rest-api-endpoints/authentication/short-grant-code). `appId` and `appVersion` identify the application redeeming the code and are required. The optional `deviceId` feeds device-trust evaluation on the resulting session; when it is omitted, the device recorded at the time the code was minted is used instead. **Response Fields** A successful exchange returns the same `AccessTokenResponse` payload as a normal sign-in: `accessToken`, `mdAccessToken`, `expirationTime`, `userId`, `name`, and the account-status flags. <Info>There is no refresh token on this response. The session is renewed the same way any other session is: with [`renewAccessToken`](/api/rest-api-endpoints/authentication/renew-access-token) before `expirationTime` elapses.</Info> **Sample Call** ```bash curl -X POST "https://live.tradovateapi.com/v1/auth/exchangeshortgrantcode" \ -H "Content-Type: application/json" \ -d '{ "code": "<code from /auth/shortgrantcode>", "appId": "NinjaTrader Web", "appVersion": "1.0", "deviceId": "<client device id>" }' ``` ```json { "accessToken": "…", "mdAccessToken": "…", "expirationTime": "2026-08-05T18:20:00.000Z", "userId": 12345, "name": "trader-name" } ``` **Common Failure Scenarios** - The code was already redeemed, expired, or was superseded by a newer code for the same user. - The user's IP at redemption differs from the `expectedClientIp` the code was bound to. - The request originated from a host outside the NinjaTrader-owned origins. - The account is inactive, is an organization administrator, or is no longer in the partner's organization. - The partner configuration was disabled or archived between minting and redemption. - Repeated failures tripped the rate limit, which responds with a time-penalty body rather than an HTTP error. **Error Messages** | `errorText` | Trigger | |-------------|---------| | `"Invalid code"` | The code is unrecognized, expired, already redeemed, or superseded by a newer code. All four cases share this message. | | `"Access denied from another IP"` | The resolved client IP does not match the code's `expectedClientIp` | | `"Access denied for <origin>"` | The request came from an origin outside the allowed NinjaTrader domains | | `"This functionality is not available to administrators"` | The account is an organization administrator or not a trader | | `"This functionality is not available to locked users"` | The account is inactive | | `"Access denied"` | A partner-policy rejection: the configuration is disabled or archived, the user is no longer bound to the partner's organization, the provider key is unknown, or mutual-TLS verification failed | Device-trust evaluation also runs on the resulting session, so a user with two-factor authentication enabled can be denied at redemption for an untrusted device or a new machine. Those denials carry their own `errorText` values, which are shared with the normal sign-in flow rather than specific to this endpoint.

Request

This endpoint expects an object.
codestringRequired<=8192 characters
appIdstringRequired<=64 characters
appVersionstringRequired<=64 characters
deviceIdstringOptional<=64 characters

Response

AccessTokenResponse
errorTextstringOptional<=8192 characters

Non-empty if the request failed

hibpHintenumOptional
EmailAndPasswordCompromised, PasswordCompromised
accessTokenstringOptional<=8192 characters
expirationTimedatetimeOptional
passwordExpirationTimedatetimeOptional
userStatusenumOptional
Active, Closed, Initiated, TemporaryLocked, UnconfirmedEmail
userIdlongOptional
namestringOptional<=64 characters
hasLivebooleanOptional
hasSimPlusbooleanOptional
showKIDsbooleanOptional