> 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.

# Rate Limits

Understanding and respecting API rate limits ensures reliable, high-performance integrations that scale with your prop firm's growth.

## Rate Limit Overview

The Tradovate API implements a **two-tier rate limiting system**:

1. **User-Level Limits**: Applied per user (by user ID) across all endpoints
2. **Endpoint-Level Limits**: Applied per IP address for specific endpoints

When an endpoint rate limit is exceeded, the user must wait the amount of seconds specified in the `p-time` before making another request of the same type.

## Considerations

* **Two-tier rate limiting**: Both user-level limits (based on authentication) and endpoint-level limits apply simultaneously. Your requests must respect both.
* **Sliding time window**: Endpoint limits use a rolling 1-hour window, not a fixed hourly reset.
* **Shared IP limits**: Endpoint limits are tracked by IP address. If you're behind a shared IP (corporate network, VPN, shared hosting), you may share limits with other clients.
* **Back-off behavior**: Repeated violations may extend the required wait time before you can retry.
* **Request counting**: Some endpoints count all requests toward the limit, while others only count failed requests. See the "Requests Counted" column in each table.

## Rate Limits

### User-Level Rate Limits

| **User Type**     | **Per Hour** | **Per Minute** | **Per Second** |
| ----------------- | ------------ | -------------- | -------------- |
| **Anonymous**     | 1,000        | 1,000          | 100            |
| **Authenticated** | 5,000        | 5,000          | 5,000          |

### Endpoint-Level Rate Limits

**Note:** *Requests Counted* indicates which requests count toward the hourly limit: All requests or failed requests only.

**Note:** Endpoints showing "n/a" do not have specific endpoint-level limits. They follow the standard user-level rate limits.

#### Authentication Endpoints

| **Endpoint**         | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| -------------------- | -------------- | ------------------ | -------------------- |
| `accesstokenrequest` | 5              | 15                 | Failed only          |
| `renewaccesstoken`   | 15             | 15                 | All                  |
| `me`                 | 10             | 30                 | Failed only          |

#### User Management Endpoints

| **Endpoint**               | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| -------------------------- | -------------- | ------------------ | -------------------- |
| `createevaluationusers`    | 100            | 1                  | Failed only          |
| `createevaluationaccounts` | 100            | 1                  | Failed only          |
| `canceleverything`         | n/a            | n/a                | n/a                  |
| `syncrequest`              | 300            | 3                  | All                  |

#### Customer Application / Subaccount Endpoints

| **Endpoint**                             | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| ---------------------------------------- | -------------- | ------------------ | -------------------- |
| `createpartnersubaccountrequest`         | 250            | 30                 | All                  |
| `submitpartnersubaccountdocument`        | 750            | 30                 | All                  |
| `submitcustomerapplicationdocument`      | 30             | 30                 | All                  |
| `getpartnersubaccountrequeststatus`      | n/a            | n/a                | n/a                  |
| `getpartnersubaccountdocumentuploadurls` | n/a            | n/a                | n/a                  |
| `checkduplicate`                         | n/a            | n/a                | n/a                  |

#### Trading Permission Endpoints

| **Endpoint**               | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| -------------------------- | -------------- | ------------------ | -------------------- |
| `requesttradingpermission` | 20             | 2                  | Failed only          |
| `revoketradingpermission`  | n/a            | n/a                | n/a                  |
| `revoketradingpermissions` | n/a            | n/a                | n/a                  |

#### Account & Risk Management Endpoints

| **Endpoint**                | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| --------------------------- | -------------- | ------------------ | -------------------- |
| `resetdemoaccountstate`     | n/a            | n/a                | n/a                  |
| `switchriskcategory`        | 5,000          | 1                  | All                  |
| `setadminautoliqaction`     | n/a            | n/a                | n/a                  |
| `updatemaxnetliq`           | n/a            | n/a                | n/a                  |
| `useraccountautoliq/update` | n/a            | n/a                | n/a                  |
| `updateuserautoliq`         | n/a            | n/a                | n/a                  |
| `updateuserautoliqs`        | n/a            | n/a                | n/a                  |
| `setdemohalt`               | 20             | 5                  | All                  |
| `changedemobalance`         | 1,000          | 10                 | All                  |

#### Subscription & Entitlement Endpoints

| **Endpoint**                 | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| ---------------------------- | -------------- | ------------------ | -------------------- |
| `addmarketdatasubscription`  | n/a            | n/a                | n/a                  |
| `addtradovatesubscription`   | n/a            | n/a                | n/a                  |
| `addentitlementsubscription` | n/a            | n/a                | n/a                  |

#### Admin Alert Endpoints

| **Endpoint**                           | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| -------------------------------------- | -------------- | ------------------ | -------------------- |
| `adminalertsignal/deps`                | n/a            | n/a                | n/a                  |
| `adminalertsignal/completealertsignal` | n/a            | n/a                | n/a                  |

#### Workspace Template Endpoints

| **Endpoint**                | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| --------------------------- | -------------- | ------------------ | -------------------- |
| `getorgworkspacetemplate`   | n/a            | n/a                | n/a                  |
| `workspacetemplate/create`  | n/a            | n/a                | n/a                  |
| `workspacetemplate/find`    | n/a            | n/a                | n/a                  |
| `workspacetemplate/finds`   | n/a            | n/a                | n/a                  |
| `workspacetemplate/item`    | n/a            | n/a                | n/a                  |
| `workspacetemplate/items`   | n/a            | n/a                | n/a                  |
| `workspacetemplate/list`    | n/a            | n/a                | n/a                  |
| `workspacetemplate/suggest` | n/a            | n/a                | n/a                  |
| `workspacetemplate/update`  | n/a            | n/a                | n/a                  |

#### Contact Info Endpoints

| **Endpoint**                    | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| ------------------------------- | -------------- | ------------------ | -------------------- |
| `contactinfo/updatecontactinfo` | n/a            | n/a                | n/a                  |

#### Order Endpoints

| **Endpoint** | **Limit/Hour** | **Back-off (sec)** | **Requests Counted** |
| ------------ | -------------- | ------------------ | -------------------- |
| `dryrun`     | 500            | 1                  | All                  |

## Rate Limit Responses

### User-Level Rate Limits Exceeded (429 Response)

For general rate limit violations, when a limit is reached, the server stops handling requests for a period of time and responds to each new request with a 429 status code.

**HTTP Response**

```http
HTTP/1.1 429 Too Many Requests
Content-Length: 0
```

**WebSocket Response**

```json
[{"s": 429, "i": "request_id"}]
```

#### Handling 429 Responses

* Wait 1 hour before sending another request
* **Cannot be resolved programmatically** from third-party applications

### Endpoint-Level Rate Limits Exceeded (Penalty Ticket)

For endpoint-level rate limit violations, the system may return a penalty ticket (p-ticket) returned in **successful HTTP responses** (200 OK), not as 429 errors. When you receive a penalty ticket, the request was not handled and the server has imposed a time penalty.

* `p-ticket`: Encrypted penalty token tied to your IP address - **must be included as an additional parameter in the request body's JSON when retrying**
* `p-time`: Penalty duration in seconds - **wait this long before retrying the call**
* `p-captcha`: Whether reCAPTCHA verification is required (optional field) - **when `true`, the operation cannot be tried again from a third party application and users should be alerted that they should try the operation again in an hour**
* `p-message`: Description of the specific limit that was exceeded - for example, `"Rate limit exceeded: more than 20 requests per hour"`

**HTTP Response**

```json
{
    "p-ticket": "encrypted_ticket_string",
    "p-time": 15,
    "p-captcha": true,
    "p-message": "Rate limit exceeded: more than 20 requests per hour"
}
```

**WebSocket Response**

```
a[{"s":200,"i":"42","d":{"p-ticket":"abc123xyz","p-time":15,"p-message":"Rate limit exceeded: more than 20 requests per hour"}}]
```

#### Handling Penalty Tickets (P-Tickets)

When you receive a penalty ticket response:

1. **Review the `p-message`**: Note the specific limit that was exceeded
2. **Check for `p-captcha`**: If `true`, stop immediately and inform users to retry in 1 hour and complete a CAPTCHA manually (cannot be resolved programmatically from third-party applications)
3. **Wait `p-time` seconds**: The client can retry the call in `p-time` seconds
4. **Retry with `p-ticket`**: Include `p-ticket` as an additional parameter in the request body's JSON along with your original request data

**Note:** One scenario where `p-captcha: true` can occur is upon too many authentication requests with sent bad data (incorrect username or password).

**Authentication Failures**: Multiple failed login attempts will trigger increasingly severe rate limits, potentially leading to reCAPTCHA requirements that cannot be bypassed programmatically.

\<CodeGroup>

```javascript JavaScript
const waitForMs = (ms) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
};

const handleRetry = async (data, json) => {
  const ticket = json['p-ticket'];
  const time = json['p-time'];
  const captcha = json['p-captcha'];
  const message = json['p-message'];

  if (message) console.log(`Rate limit reason: ${message}`);

  if (captcha) {
    console.error('Captcha present, cannot retry auth request via third party application. Please try again in 1 hour.');
    return;
  }

  console.log(`Time Penalty present. Retrying operation in ${time}s`);

  await waitForMs(time * 1000);
  
  // Retry with original data plus p-ticket
  await makeRequest({ ...data, 'p-ticket': ticket });
};

const connect = async (data) => {
  const authResponse = await tvPost('/auth/accesstokenrequest', data, false);

  // Check for penalty ticket in response body
  if (authResponse['p-ticket']) {
    return await handleRetry(data, authResponse);
  }

  // Process successful response
  const { accessToken, expirationTime } = authResponse;
  setAccessToken(accessToken, expirationTime);
};
```

```typescript TypeScript
interface PenaltyTicket {
  'p-ticket': string;
  'p-time': number;
  'p-captcha'?: boolean;
  'p-message': string;
}

const waitForMs = (ms: number): Promise<void> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, ms);
  });
};

const handleRetry = async (data: any, json: PenaltyTicket): Promise<void> => {
  const ticket = json['p-ticket'];
  const time = json['p-time'];
  const captcha = json['p-captcha'];
  const message = json['p-message'];

  console.log(`Rate limit reason: ${message}`);

  if (captcha) {
    console.error('Captcha present, cannot retry auth request via third party application. Please try again in 1 hour.');
    return;
  }

  console.log(`Time Penalty present. Retrying operation in ${time}s`);

  await waitForMs(time * 1000);
  
  // Retry with original data plus p-ticket
  await makeRequest({ ...data, 'p-ticket': ticket });
};

const connect = async (data: any): Promise<void> => {
  const authResponse = await tvPost('/auth/accesstokenrequest', data, false);

  // Check for penalty ticket in response body
  if (authResponse['p-ticket']) {
    return await handleRetry(data, authResponse);
  }

  // Process successful response
  const { accessToken, expirationTime } = authResponse;
  setAccessToken(accessToken, expirationTime);
};
```

```python Python
import time
from typing import Dict, Any, Optional

def wait_for_seconds(seconds: float) -> None:
    time.sleep(seconds)

def handle_retry(data: Dict[str, Any], json: Dict[str, Any]) -> None:
    ticket = json.get('p-ticket')
    wait_time = json.get('p-time')
    captcha = json.get('p-captcha')
    message = json.get('p-message')

    print(f'Rate limit reason: {message}')

    if captcha:
        print('Captcha present, cannot retry auth request via third party application. Please try again in 1 hour.')
        return

    print(f'Time Penalty present. Retrying operation in {wait_time}s')

    wait_for_seconds(wait_time)
    
    # Retry with original data plus p-ticket
    retry_data = {**data, 'p-ticket': ticket}
    make_request(retry_data)

def connect(data: Dict[str, Any]) -> None:
    auth_response = make_request('/auth/accesstokenrequest', data)

    # Check for penalty ticket in response body
    if 'p-ticket' in auth_response:
        return handle_retry(data, auth_response)

    # Process successful response
    access_token = auth_response.get('accessToken')
    expiration_time = auth_response.get('expirationTime')
    set_access_token(access_token, expiration_time)
```

\</CodeGroup>

## Rate Limits Best Practices

### Do's ✅

1. **Check response bodies for penalty tickets** - Even successful (200 OK) responses may contain `p-ticket` fields
2. **Wait the full `p-time` duration** before retrying penalty ticket requests
3. **Include `p-ticket` in retry requests** - Add it to the original request body when retrying
4. **Check `p-captcha` first** - Always verify this field before attempting retries
5. **Cache responses** appropriately to reduce API calls
6. **Use batch endpoints** when available
7. **Prioritize critical requests** (orders, risk management)
8. **Set up monitoring and alerting** for rate limit usage
9. **Handle authentication failures gracefully** - Track failed attempts to prevent lockouts

### Don'ts ❌

1. **Don't ignore penalty tickets** - Always check response bodies for `p-ticket` fields, even on successful responses
2. **Don't retry when `p-captcha: true`** - Stop immediately and inform users to wait 1 hour
3. **Don't retry before waiting `p-time` seconds** - Respect the full penalty duration
4. **Don't forget to include `p-ticket`** - The retry request must include the penalty ticket
5. **Don't make unnecessary API calls** - Cache when possible to avoid hitting limits
6. **Don't exceed rate limits consistently** - This may result in temporary blocks or reCAPTCHA requirements
7. **Don't implement infinite retry loops** - Set maximum retry attempts
8. **Don't skip authentication rate limits** - These are stricter and may trigger reCAPTCHA

## Requesting Higher Limits

For high-volume integrations, you can request increased rate limits:

### Eligibility Criteria

* **Established partnership** with proven integration
* **Proper rate limit handling** implemented
* **Business justification** for higher limits
* **Technical review** of integration architecture

## Support

Need help with rate limiting?

* **[Partner Success Team](mailto:partners@tradovate.com)** - Rate limit increase requests
* **[Support Center](https://support.tradovate.com)** - Troubleshooting guides

***