> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://partner.ninjatrader.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://partner.ninjatrader.com/_mcp/server.

# SSO with ShortGrantCode

Once your backend can mint a partner access token, the short grant code handoff sends a signed-in user to a NinjaTrader-hosted page, such as the trader dashboard or an account page, without a second login. The access token stays on your backend and only a short-lived, single-use code travels through the browser.

This flow builds on [Partner Token](/connect/overview/partner-integration/partner-token) rather than replacing it. Complete that integration first, since this layer needs the partner access token it produces. If you are embedding a NinjaTrader page inside your own application instead of redirecting to one, use [the iframe flow](/connect/overview/partner-integration/embedding-funding-page-with-i-frame), which passes a token in the URL fragment rather than a code.

## How the Flow Works

1. Your backend calls [`shortGrantCode`](/connect/api/rest-api-endpoints/authentication/short-grant-code) with the partner access token it holds for that user.
2. NinjaTrader returns `code` and `expires_in`.
3. Your backend redirects the user's browser to the NinjaTrader application with the code attached, and the application redeems it with [`exchangeShortGrantCode`](/connect/api/rest-api-endpoints/authentication/exchange-short-grant-code).

## Prerequisites

This flow uses the same onboarding as the base integration, with nothing extra. Your issuer, signing keys and JWKS URL, audience, API hostnames, and pilot user IDs are all you need. NinjaTrader confirms the application host you should redirect users to at the same time it confirms your API hostnames.

## Minting a Code

```bash
curl -sS -X POST "https://live.tradovateapi.com/v1/auth/shortgrantcode" \
  -H "Authorization: Bearer $PARTNER_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"ttl\": 30, \"expectedClientIp\": \"$END_USER_IP\"}"
```

```json
{
  "code": "…",
  "expires_in": 30
}
```

`expectedClientIp` is the address from which the user's browser will reach the exchange endpoint, resolved through your own load-balancer-aware logic. The code is bound to it, and redemption from anywhere else is rejected.

**Warning:** `expectedClientIp` is what selects partner mode, and the schema does not require it. Omitting it does not return an error. The request silently falls back to the non-partner path, which binds the code to **your backend's** address instead of the user's, so the redirect then fails at redemption. Always send it.

Note the underscore in `expires_in`. Read the lifetime back from that field rather than assuming it, because `ttl` is a request and not a guarantee: the server clamps it to the per-environment maximum, which is 30 seconds in production, and omitting `ttl` or sending zero or less gives 15 seconds.

Other properties worth knowing:

* `code` is opaque. Do not parse it, log it, or hold it longer than the redirect needs.
* Codes are single-use, and minting a second code for the same user invalidates the first.
* Unused codes expire on their own, so there is nothing to clean up if the user closes the tab.
* The partner access token used to mint a code identifies the same user as the `sub` in your original assertion. You cannot mint a code for one user with another user's token.

Mint the code at the moment you are ready to redirect, not in advance.

## Redirecting the User

Pass the code as the `h` query parameter:

```
HTTP/1.1 302 Found
Location: https://NT_APP_HOST/?h=CODE
```

Or in the browser:

```javascript
window.location.assign(`https://${ntAppHost}/?h=${encodeURIComponent(code)}`);
```

The host must be the one NinjaTrader confirmed during onboarding. An unrecognized host fails at redemption, because the exchange only accepts NinjaTrader-owned origins.

A query parameter is appropriate here even though the code is sensitive: its lifetime is measured in seconds, it is single-use and redeemed within about a second of page load, it is bound to the redeeming client's address, and the application strips `h` from the URL immediately after reading it. The URL fragment is reserved for the iframe flow, where the value that travels is a full access token rather than a one-shot code.

Keep the redirect URL clean:

* Do not include the partner access token, the assertion, a user ID, or any other partner-side identifier. The `h` parameter is all that is needed.
* Do not add tracking parameters or custom query strings, which may be treated as misuse.
* Do not use a meta refresh or a delayed link. The code can expire before the user acts, so redirect immediately.

## Rate Limits

| Endpoint                                                                                             | Limit                                                                                                                                                                                                                                                                                                                                            |
| ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`shortGrantCode`](/connect/api/rest-api-endpoints/authentication/short-grant-code)                  | No endpoint-specific time penalty. Partner-mode requests are limited per partner organization, 1,000 per hour by default, measured over a sliding window. Exceeding it returns `HTTP 429` with a plain-text body. Treat that `429` as transient and back off; there is no `Retry-After` header today, so pace retries against the hourly window. |
| [`exchangeShortGrantCode`](/connect/api/rest-api-endpoints/authentication/exchange-short-grant-code) | 10 requests per hour with a 3-second back-off, counting failed requests only. This is an anti-abuse limit on the redemption side, which the NinjaTrader application calls rather than you. Successful first-try redemptions do not consume budget. Exceeding it returns a time-penalty body on an `HTTP 200` rather than a `429`.                |

Quotas are tier-dependent and confirmed during onboarding.

## Handling Errors

The two endpoints do not share a response shape, and neither signals failure through the status code alone.

**Warning:** Both endpoints return `HTTP 200` for policy rejections as well as successes, so do not branch on the status code. Test for success by checking both `errorText` and whether a token is present: at redemption, a restricted session returns an `accessToken` alongside a non-empty `errorText` carrying a warning, so `errorText` alone misreads that case as a failure. `HTTP 401` means only that the access token is missing or expired, and `HTTP 400` means the request failed field validation.

At the minting step, every partner-policy rejection returns the same message, `"Short grant codes are not available for this account"`. The specific reason is deliberately not disclosed on the wire and is recorded only in NinjaTrader's logs. The underlying causes are:

| Reason                                                 | What to do                                                                  |
| ------------------------------------------------------ | --------------------------------------------------------------------------- |
| The caller's organization has no partner configuration | Confirm onboarding completed and the user belongs to the bound organization |
| The configuration is disabled or archived              | Contact NinjaTrader operations                                              |
| The user is not a trader account                       | Partner SSO is for end traders only                                         |
| The user is an organization administrator              | Use a trader account, since admin sessions go through direct sign-in        |
| The account is inactive                                | Your provisioning is out of sync with NinjaTrader                           |

At redemption, failures do surface distinct messages, including an unrecognized or already-redeemed code, an address that does not match the one the code was bound to, and an origin outside the NinjaTrader-owned hosts. The full list is on [`exchangeShortGrantCode`](/connect/api/rest-api-endpoints/authentication/exchange-short-grant-code).

**Warning:** A code that reaches validation is consumed even when the exchange then fails. Retrying the same code after an address or account failure reports an invalid code rather than the original reason, which hides the real cause. Mint a fresh code for every attempt.

Because the address comparison is an exact string match, two textual forms of the same IPv6 address do not match. Emit `expectedClientIp` in the form the client will present at redemption.

## Testing

Mint a code bound to your egress address, then open the redirect in a browser and confirm you land in the NinjaTrader application already signed in. Worth exercising before go-live:

| Case                                        | Expected                                                    |
| ------------------------------------------- | ----------------------------------------------------------- |
| Valid code, redeemed from the bound address | The user lands signed in                                    |
| The same code redeemed twice                | The second attempt reports an invalid code                  |
| Redemption from a different address         | Access is denied for the address                            |
| A code left until after `expires_in`        | The code is reported invalid                                |
| `expectedClientIp` omitted                  | Minting succeeds, and the redirect then fails at redemption |

## Going Live

* The base integration is complete on staging and can mint partner access tokens for pilot users.
* The application host for production is confirmed and configured on your side.
* `expectedClientIp` is populated from the user's address, verified behind your load balancer.
* The redirect happens immediately after minting, with no delay or user interaction in between.
* `HTTP 429` and redemption failures are handled, with a fresh code minted on retry.

## Next Steps

* [Partner Token](/connect/overview/partner-integration/partner-token): the assertion exchange this flow builds on.
* [`shortGrantCode`](/connect/api/rest-api-endpoints/authentication/short-grant-code) and [`exchangeShortGrantCode`](/connect/api/rest-api-endpoints/authentication/exchange-short-grant-code): full request, response, and error reference.