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:

1{
2 "errorText": "Operation failed",
3 "ok": false
4}

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

1{
2 "violations": [
3 {
4 "constraint": "required_field_missing",
5 "value": "",
6 "description": "First Name should be specified"
7 }
8 ]
9}

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.
423423 preceeds a 429 error - this response means you’re about to be locked out of the API.
429429 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.
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:

1// The API returns 401 with a simple error message
2// Your client should handle token refresh automatically
3try {
4 const response = await fetch("/auth/me", {
5 headers: { Authorization: `Bearer ${token}` },
6 });
7
8 if (response.status === 401) {
9 // Token expired - refresh and retry
10 await refreshToken();
11 // Retry original request
12 }
13} catch (error) {
14 console.error("Authentication failed");
15}

Invalid Credentials:

1// 401 response with error message
2if (response.status === 401) {
3 throw new Error("Please check your credentials");
4}

Validation Errors

The API returns validation errors in a violations array format:

1// Example validation error response
2{
3 "violations": [
4 {
5 "constraint": "required_field_missing",
6 "value": "",
7 "description": "First Name should be specified"
8 },
9 {
10 "constraint": "length_exceeded",
11 "value": "very long string...",
12 "description": "First Name should be no longer than 64"
13 }
14 ]
15}

Handle validation errors:

1const handleValidationErrors = (response) => {
2 if (response.violations) {
3 response.violations.forEach((violation) => {
4 console.error(`${violation.constraint}: ${violation.description}`);
5 });
6 }
7};

Trading-Specific Error Codes

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

Response
1{
2 "failureReason": "AccountClosed",
3 "failureText": "string",
4 "orderId": 1
5}

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:

1{
2 "errorText": "Email already registered",
3 "errorCode": "EmailAlreadyRegistered",
4 "emailVerified": false
5}

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:

1{
2 "errorText": "Insufficient funds for subscription",
3 "errorCode": "InsufficientFunds",
4 "tradovateSubscription": null
5}

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:

1// Websocket error format: a[{"s":statusCode,"i":"requestId"}]
2const handleWebsocketError = (message) => {
3 try {
4 const parsed = JSON.parse(message.substring(2, message.length - 1)); // Remove 'a[' and ']'
5 if (parsed.s >= 400) {
6 console.error(`Websocket error: ${parsed.s} for request ${parsed.i}`);
7
8 if (parsed.s === 401) {
9 // Authentication required
10 reconnectWithAuth();
11 }
12 }
13 } catch (e) {
14 console.error("Failed to parse websocket error:", message);
15 }
16};

Connection State Management

1class WebsocketManager {
2 constructor() {
3 this.connectionState = "disconnected";
4 this.reconnectAttempts = 0;
5 this.maxReconnectAttempts = 5;
6 }
7
8 onError(error) {
9 console.error("Websocket error:", error);
10 this.connectionState = "error";
11
12 if (this.reconnectAttempts < this.maxReconnectAttempts) {
13 const delay = Math.pow(2, this.reconnectAttempts) * 1000;
14 setTimeout(() => this.reconnect(), delay);
15 this.reconnectAttempts++;
16 }
17 }
18
19 onClose(event) {
20 this.connectionState = "disconnected";
21 if (!event.wasClean) {
22 // Unexpected disconnect - attempt reconnection
23 this.onError(new Error("Connection closed unexpectedly"));
24 }
25 }
26}

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:

1class TradovateClient {
2 //...
3 async makeRequest(endpoint, options = {}) {
4 try {
5 const response = await fetch(`${this.baseUrl}${endpoint}`, {
6 ...options,
7 headers: {
8 Authorization: `Bearer ${this.accessToken}`,
9 "Content-Type": "application/json",
10 ...options.headers,
11 },
12 });
13
14 if (!response.ok) {
15 const error = await this.parseError(response);
16 // utilize a service to push errors to a logging solution of your choice
17 this.errorTracker.trackError(error, { endpoint, options });
18 throw error;
19 }
20
21 return await response.json();
22 } catch (error) {
23 // determine if retry is possible - say on p-ticket error or on a failed
24 // API action. Likely you won't retry for a query with fixed data (response won't change)
25 if (this.shouldRetry(error)) {
26 return this.retryHandler.executeWithRetry(() => this.makeRequest(endpoint, options));
27 }
28 throw error;
29 }
30 }
31
32 async parseError(response) {
33 let errorData;
34 try {
35 errorData = await response.text();
36 // Try to parse as JSON for validation errors
37 if (errorData.startsWith("{")) {
38 errorData = JSON.parse(errorData);
39 }
40 } catch (e) {
41 errorData = "Unknown error";
42 }
43
44 return {
45 status: response.status,
46 // transform error messages into human-readable responses
47 message: this.getErrorMessage(errorData),
48 data: errorData,
49 };
50 }
51}