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 an 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:

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

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

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

HTTP Status Codes

CodeDescription
400A 400 level response usually means that the request was formatted incorrectly.
401A 401 means that you don’t have the correct permissions to view this item.
404A 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.
423A 423 (Locked) is returned when a penalty ticket requires a reCAPTCHA (p-captcha) and the retry doesn’t include a solved reCAPTCHA. A server-to-server integration can’t answer the reCAPTCHA, so the action stays locked until the endpoint’s elevated call count clears (about an hour).
429A 429 (Too Many Requests) means you’ve exceeded the overall, user-level request rate limit. The server stops handling your requests for a period, and continuing to send requests during that window resets it. A 429 differs from a penalty ticket, which comes back as a 200. See Penalty Tickets.
500500 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:

// 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:

// 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:

// 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:

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
{
"failureReason": "Success",
"failureText": "",
"orderId": 123456789
}

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:

{
"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:

{
"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:

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

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:

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,
};
}
}