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

# Error Handling

Robust error handling is essential for building reliable integrations with the Tradovate Partner API. This guide covers error types, response formats, retry strategies, and best practices based on the actual implementation.

## Error Response Format

The Tradovate API uses different error response formats depending on the endpoint type and the error level. HTTP level errors will produce and HTTP error code, while business-level errors will produce a 200 level response with more details about the error. Many endpoints use the `SimpleResponse` schema which includes both success status and error information:

```json
{
  "errorText": "Operation failed",
  "ok": false
}
```

However, we also utilize a validation API, which produces 200 level errors with meaningful field-level validity checks:

```json
{
  "violations": [
    {
      "constraint": "firstName",
      "value": "",
      "description": "First Name should be specified"
    }
  ]
}
```

## HTTP Status Codes

| Code  | Description                                                                                                                                                                                                                                           |
| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | A `400` level response usually means that the request was formatted incorrectly.                                                                                                                                                                      |
| `401` | A `401` means that you don't have the correct permissions to view this item.                                                                                                                                                                          |
| `404` | A `404` HTTP code means that some portion of the response could not be found, or the endpoint itself is not defined. For example, when sending a request to `/user/item?id=1234`, a 404 will designate that the user with ID 1234 could not be found. |
| `423` | `423` preceeds a `429` error - this response means you're about to be locked out of the API.                                                                                                                                                          |
| `429` | `429` is the re-captcha response. Receiving a `429` means that you are locked out of the API on a rolling 1-hour window - requests made during the window will reset the lockout.                                                                     |
| `500` | `500` is an internal server error. This means that an error was produced by the message sent to our server. One common case of a `500` level error is an internal timeout being hit.                                                                  |

## Common Error Scenarios

### Authentication Errors

**Expired Access Token:**

```javascript
// The API returns 401 with a simple error message
// Your client should handle token refresh automatically
try {
  const response = await fetch("/auth/me", {
    headers: { Authorization: `Bearer ${token}` },
  });

  if (response.status === 401) {
    // Token expired - refresh and retry
    await refreshToken();
    // Retry original request
  }
} catch (error) {
  console.error("Authentication failed");
}
```

**Invalid Credentials:**

```javascript
// 401 response with error message
if (response.status === 401) {
  throw new Error("Please check your credentials");
}
```

### Validation Errors

The API returns validation errors in a violations array format:

```javascript
// Example validation error response
{
  "violations": [
    {
      "constraint": "firstName",
      "value": "",
      "description": "First Name should be specified"
    },
    {
      "constraint": "firstName",
      "value": "very long string...",
      "description": "First Name & Last Name should be no longer than 60"
    }
  ]
}
```

Handle validation errors:

```javascript
const handleValidationErrors = (response) => {
  if (response.violations) {
    response.violations.forEach((violation) => {
      console.error(`${violation.constraint}: ${violation.description}`);
    });
  }
};
```

### Trading-Specific Error Codes

Order placement and trading operations return specific failure reasons using the `PlaceOrderResult` schema:

### Response (200)

```json
{
  "failureReason": "AccountClosed",
  "failureText": "string",
  "orderId": 1
}
```

**Trading Error Codes (from OpenAPI spec):**

* `AccountClosed` - Account is closed
* `AdvancedTrailingStopUnsupported` - Advanced trailing stop not supported
* `AnotherCommandPending` - Another command is pending
* `BackMonthProhibited` - Back month trading prohibited
* `ExecutionProviderNotConfigured` - Execution provider not configured
* `ExecutionProviderUnavailable` - Execution provider unavailable
* `InvalidContract` - Contract not found or invalid
* `InvalidPrice` - Price outside acceptable range
* `LiquidationOnly` - Account in liquidation-only mode
* `LiquidationOnlyBeforeExpiration` - Liquidation-only before expiration
* `MaxOrderQtyIsNotSpecified` - Maximum order quantity not specified
* `MaxOrderQtyLimitReached` - Order quantity exceeds limits
* `MaxPosLimitMisconfigured` - Position limit misconfigured
* `MaxPosLimitReached` - Position limit exceeded
* `MaxTotalPosLimitReached` - Total position limit exceeded
* `MultipleAccountPlanRequired` - Multiple account plan required
* `NoQuote` - No market data available
* `NotEnoughLiquidity` - Insufficient market liquidity
* `OtherExecutionRelated` - Other execution-related error
* `ParentRejected` - Parent order rejected
* `RiskCheckTimeout` - Risk validation timed out
* `SessionClosed` - Trading session is closed
* `Success` - Operation successful
* `TooLate` - Request too late
* `TradingLocked` - Account trading is locked
* `TrailingStopNonOrderQtyModify` - Cannot modify trailing stop order quantity
* `Unauthorized` - Insufficient permissions
* `UnknownReason` - Unknown error reason
* `Unsupported` - Operation not supported

### User Registration Error Codes

User signup operations use specific error codes via the `SignUpResponse` schema:

```json
{
  "errorText": "Email already registered",
  "errorCode": "EmailAlreadyRegistered",
  "emailVerified": false
}
```

**Signup Error Codes (from OpenAPI spec):**

* `DataError` - Data validation error
* `EmailAlreadyRegistered` - Email address already exists
* `EmailPolicyFailed` - Email policy validation failed
* `FailedRecaptcha` - ReCaptcha verification failed
* `Success` - Registration successful
* `UnknownError` - Unexpected error
* `UserAlreadyExists` - User already exists
* `WeakPassword` - Password doesn't meet requirements
* `WrongChallenge` - Challenge verification failed
* `WrongChallengeOrigin` - Challenge origin verification failed

### Subscription Error Codes

Market data and subscription operations use specific error codes:

```json
{
  "errorText": "Insufficient funds for subscription",
  "errorCode": "InsufficientFunds",
  "tradovateSubscription": null
}
```

**Subscription Error Codes (from OpenAPI spec):**

* `ConflictWithExisting` - Subscription conflict with existing plan
* `DowngradeNotAllowed` - Cannot downgrade current plan
* `IncompatibleCMEMarketDataSubscriptionPlans` - CME plan compatibility issue
* `IncorrectPaymentMethod` - Payment method issue
* `InsufficientFunds` - Insufficient account balance
* `PaymentProviderError` - Payment processing error
* `PlanDiscontinued` - Plan no longer available
* `SingleTrialOnly` - Trial already used
* `Success` - Operation successful
* `UnknownError` - Unexpected error

## Websocket Error Handling

The Websocket protocol will attempt to mimic our REST API response structure as best as possible. A WebSocket error message will be structured in this format:

```
a[{"s":401,"i":123}]
```

### Connection Errors

Websocket connections have specific error handling:

```javascript
// Websocket error format: a[{"s":statusCode,"i":"requestId"}]
const handleWebsocketError = (message) => {
  try {
    const parsed = JSON.parse(message.substring(2, message.length - 1)); // Remove 'a[' and ']'
    if (parsed.s >= 400) {
      console.error(`Websocket error: ${parsed.s} for request ${parsed.i}`);

      if (parsed.s === 401) {
        // Authentication required
        reconnectWithAuth();
      }
    }
  } catch (e) {
    console.error("Failed to parse websocket error:", message);
  }
};
```

### Connection State Management

```javascript
class WebsocketManager {
  constructor() {
    this.connectionState = "disconnected";
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }

  onError(error) {
    console.error("Websocket error:", error);
    this.connectionState = "error";

    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      const delay = Math.pow(2, this.reconnectAttempts) * 1000;
      setTimeout(() => this.reconnect(), delay);
      this.reconnectAttempts++;
    }
  }

  onClose(event) {
    this.connectionState = "disconnected";
    if (!event.wasClean) {
      // Unexpected disconnect - attempt reconnection
      this.onError(new Error("Connection closed unexpectedly"));
    }
  }
}
```

## Best Practices

### 1. Implement Comprehensive Error Handling

Be sure to cover all possible error cases for any domains of the API that your application will touch. Here's an example of what this implementation might look like in JavaScript:

```javascript
class TradovateClient {
  //...
  async makeRequest(endpoint, options = {}) {
    try {
      const response = await fetch(`${this.baseUrl}${endpoint}`, {
        ...options,
        headers: {
          Authorization: `Bearer ${this.accessToken}`,
          "Content-Type": "application/json",
          ...options.headers,
        },
      });

      if (!response.ok) {
        const error = await this.parseError(response);
        // utilize a service to push errors to a logging solution of your choice
        this.errorTracker.trackError(error, { endpoint, options });
        throw error;
      }

      return await response.json();
    } catch (error) {
      // determine if retry is possible - say on p-ticket error or on a failed
      // API action. Likely you won't retry for a query with fixed data (response won't change)
      if (this.shouldRetry(error)) {
        return this.retryHandler.executeWithRetry(() => this.makeRequest(endpoint, options));
      }
      throw error;
    }
  }

  async parseError(response) {
    let errorData;
    try {
      errorData = await response.text();
      // Try to parse as JSON for validation errors
      if (errorData.startsWith("{")) {
        errorData = JSON.parse(errorData);
      }
    } catch (e) {
      errorData = "Unknown error";
    }

    return {
      status: response.status,
      // transform error messages into human-readable responses
      message: this.getErrorMessage(errorData),
      data: errorData,
    };
  }
}
```