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

# Authentication & Access

Before a client can reach a protected resource, it must obtain an access token. Your client then sends that token on every request using the `Bearer` authentication scheme. You can get a token by exchanging your credentials directly, by using a social login, or by delegating authentication to OAuth.

For runnable walkthroughs, see the [JavaScript example project](https://github.com/tradovate/example-api-js), the [C# API and WebSocket guide](https://github.com/tradovate/example-api-csharp-trading), the [OAuth example](https://github.com/tradovate/example-api-oauth), and the [API FAQ and code samples](https://github.com/tradovate/example-api-faq).

## Servers

The API is split across hostnames by the service each one provides. Use the host that corresponds to what you need:

| Host                    | Use                           |
| ----------------------- | ----------------------------- |
| `live.tradovateapi.com` | Live trading                  |
| `demo.tradovateapi.com` | Demo (simulation) environment |
| `md.tradovateapi.com`   | Market data feed              |

Code examples in these docs default to the Demo host so you can test safely. Swap in `live.tradovateapi.com` for Live. In the API Reference, use the environment selector to switch the playground and generated snippets between Demo and Live.

The API is a relaxed REST API: it accepts either `GET` or `POST` for most endpoints rather than enforcing a method. As a rule of thumb, send a `POST` when the request carries a JSON body and a `GET` when it doesn't. All responses are returned in JSON.

## Access Tokens

To request a token, exchange your credentials with the `accessTokenRequest` operation. The response includes the token and an `expirationTime` — the token stays valid until then.

```bash title="cURL"
curl -X POST https://demo.tradovateapi.com/v1/auth/accesstokenrequest \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
        "name": "<your credentials here>",
        "password": "<your credentials here>",
        "appId": "Sample App",
        "appVersion": "1.0",
        "cid": 8,
        "deviceId": "123e4567-e89b-12d3-a456-426614174000",
        "sec": "f03741b6-f634-48d6-9308-c8fb871150c2"
      }'
```

```js title="JavaScript"
const body = {
    name:       "<replace with your credentials>",
    password:   "<replace with your credentials>",
    appId:      "Sample App",
    appVersion: "1.0",
    cid:        8,
    sec:        "f03741b6-f634-48d6-9308-c8fb871150c2",
    deviceId:   "123e4567-e89b-12d3-a456-426614174000"
}

const response = await fetch('https://live.tradovateapi.com/v1/auth/accesstokenrequest', {
    method: 'POST',
    mode: 'cors',
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
    },
    body: JSON.stringify(body)
})

const json = await response.json()
```

A successful response looks like this:

```json
{
    "accessToken": "<your access token>",
    "mdAccessToken": "<your market data access token>",
    "expirationTime": "2021-06-15T15:40:30.056Z",
    "userStatus": "Active",
    "userId": 15460,
    "name": "example-user",
    "hasLive": true,
    "outdatedTaC": false,
    "hasFunded": true,
    "hasMarketData": true,
    "outdatedLiquidationPolicy": false
}
```

### Token Lifetime and Sessions

An access token lasts about 90 minutes — the exact moment is in the response's `expirationTime` field. To keep a long-running application connected, extend the session by calling `renewAccessToken` about 15 minutes before the token expires, rather than requesting a brand-new token. Renewing keeps your existing session alive; repeatedly calling `accessTokenRequest` starts new sessions.

You're limited to **two concurrent sessions** per user. When a third session starts, the oldest is closed — which can drop dependent services that share a token. To avoid this:

* Use a dedicated API user that isn't also logging in to the `.tradovate.com` websites.
* Centralize token issuance in one service and distribute the same valid token to the services that depend on it.

### Two-Factor Authentication

To enable two-factor authentication for your application, supply three additional fields in the access token request body:

* `deviceId` is a string of up to 64 characters that uniquely and permanently identifies the physical device. Supplying this value is your responsibility — there's no built-in device-identification solution. For example, you can use the [DeviceId](https://github.com/MatthewKing/DeviceId) package for C#, or a package like [device-uuid](https://www.npmjs.com/package/device-uuid) for JavaScript.
* `cid` is a client app ID provided to you.
* `sec` is a secret (or API key) provided to you. Keep this key out of public access — for example, don't commit the file that holds it to a public repository.

Unlike `cid`, the `appId` and `appVersion` fields are free-form identifiers that can be displayed to end users in the trader application to show which apps are connected.

**Use two-factor authentication.** Your account — or your client's — represents real money and identity, and two-factor authentication is an industry standard in financial technology. Don't take risks with your clients' credentials.

### OAuth

If your application authenticates other users, it can delegate authentication to NinjaTrader via OAuth instead of handling their credentials directly. See [OAuth Integration](/api/oauth) for the authorization-code flow.

## Using Your Access Token

To reach the API's endpoints, follow the `Bearer` authentication scheme by setting the `Authorization` header on each request:

```bash title="cURL"
curl -X GET 'https://live.tradovateapi.com/v1/account/list' \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer <your access token>'
```

```js title="JavaScript"
const response = await fetch('https://live.tradovateapi.com/v1/account/list', {
    method: 'GET',
    headers: {
        'Authorization': `Bearer ${myAccessToken}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
})

const json = await response.json()
```

Social logins aren't currently allowed for customers with Live accounts.

## Streaming via WebSocket

WebSockets let you open an interactive session so you can receive and respond to data in real time without polling. The protocol uses the same request semantics as the REST API, adapted to the JSON format the standard WebSocket protocol expects. See [Real-Time Data via WebSocket](/api/websockets) for the full protocol.

## Request Rate Limits and Time Penalties

To protect the service, request rates are limited and time penalties are imposed when certain thresholds are crossed. There are limits on the hour, minute, and second intervals. When a limit is reached, the server stops handling requests for a period and responds with `HTTP 429`, `Too Many Requests`. The limits are generally high enough that normal operations won't reach them.

Operations known to be called infrequently — such as requesting an access token, signing up for an account, changing a password, or changing contact information — carry tighter constraints. When one of these limits is reached, the server issues a time-penalty response: a JSON object with `p-ticket` and `p-time` fields. This means the request wasn't handled and a time penalty was imposed. The client can retry after `p-time` seconds, including `p-ticket` as an additional parameter in the request body.

The response may also include a `p-captcha` field. When it's `true`, the failed operation can't be retried from a third-party application, and you should alert users to try again in about an hour. This can happen, for example, after too many authentication requests with bad credentials.

For more on rate limits, see the [API FAQ](https://github.com/tradovate/example-api-faq).

## Connection Limits

There's a limit of one simultaneous client connection per customer. Additional simultaneous connections are supported by subscription — see the trader application's connection settings for allowances and pricing. When a customer reaches their connection limit, the server disconnects the oldest connections.

## Account Risk Status

Accounts that fall below a set of health criteria may be auto-liquidated. You can review the full [liquidation policy](https://tradovate.com/liquidation-policy/) for details. The easiest way to retrieve an account's current liquidation status is the `accountRiskStatus/list` operation, which returns the calling account's status. The response includes the current status as `adminAction`, timestamps for the last auto-liquidation period and review time, a reason code for the action taken, and a counter tracking how many times the account has been auto-liquidated.

To help avoid auto-liquidation, the API exposes the same risk-management services available in the trader application. See the **Risks** endpoints in the API Reference. Some of the ways you can limit your risk include:

* Position limits (how much you can trade).
* Product limitations (what you can trade).
* Daily and weekly loss limits.
* Trailing max drawdown.

You can query your current risk limits with the `userAccountPositionLimit/deps` or `userAccountPositionLimit/ldeps` operations, supplying your account ID as a query parameter. Each returns a list of `UserAccountPositionLimit` entities — note their IDs, since you'll use them to read the paired parameters.

Your account ID is the integer entity ID, not the `LIVE12345` or `DEMO12345` string. Retrieve it from the `account/list` operation — the `id` field is the real entity ID.

To read the related risk-parameter entities (the actual values set by risk limits), use the `userAccountRiskParameter/deps` operation. It returns all parameters related to a given `UserAccountPositionLimit`. For example, a hard position limit of 30 open contracts returns a risk parameter like this:

```js
[
    {
        "id": 20745,
        "hardLimit": true,
        "userAccountPositionLimitId": 17180
    }
]
```

To set your own risk limits through the API, combine the `userAccountPositionLimit/create` and `userAccountRiskParameter/create` operations: first create a position-limit entity, then parameterize it with a risk-parameter entity. For example, to cap your maximum allowed position at 30 open contracts:

```js
const URL = 'https://demo.tradovateapi.com/v1'

const posLimitBody = {
    accountId: myAccountId,
    active: true,
    totalBy: 'Overall',
    exposedLimit: 30, // maximum open contracts
    description: 'Max Position Size',
}

const posLimitRes = await fetch(URL + '/userAccountPositionLimit/create', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': `Bearer ${myAccessToken}`
    },
    body: JSON.stringify(posLimitBody)
})

const { id } = await posLimitRes.json()

const riskParamBody = {
    hardLimit: true,
    userAccountPositionLimitId: id
}

const riskParamRes = await fetch(URL + '/userAccountRiskParameter/create', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': `Bearer ${myAccessToken}`
    },
    body: JSON.stringify(riskParamBody)
})

const riskParams = await riskParamRes.json()
```