Error Handling
Exception hierarchy and error handling in the .NET SDK
The SDK uses a hierarchical exception system, all inheriting from SsiException (and System.Exception).
Exception List
| Exception | Derives from | Description |
|---|---|---|
SsiException | Exception | Base exception. Has Code, StatusCode, ResponseBody, Headers |
AuthenticationException | SsiException | Authentication failed (bad credentials, expired token, signature error) |
SmartOtpPendingException | AuthenticationException | Smart OTP not approved yet (HTTP 202, code 401114), or polling ran out without approval |
SmartOtpRejectedException | AuthenticationException | Smart OTP rejected or invalid (401113, 401115, 401116). Terminal, no more polling. |
ReauthenticationRequiredException | AuthenticationException | The token is expired, cannot be refreshed and no new otp/transactionId was given: authenticate again |
ApiException | SsiException | API returned an error. Has Code, StatusCode, ResponseBody |
DuplicateRequestException | ApiException | Duplicate request (HTTP 409, default code "409") |
WebSocketException | SsiException | WebSocket connection or communication error |
TradingWsError | WebSocketException | Trading WebSocket: the server rejected the socket or a command. Has Status, ErrorCode, ServerMessage |
ValidationException | SsiException | Input validation error, detected on the client, nothing sent |
RateLimitException | SsiException | Request rate limit exceeded (HTTP 429). Default code "RATE_LIMITED" |
Inheritance Chain
Exception → SsiException → AuthenticationException → SmartOtpPendingException
→ SmartOtpRejectedException
→ ReauthenticationRequiredException
→ ApiException → DuplicateRequestException
→ WebSocketException → TradingWsError
→ ValidationException
→ RateLimitExceptionBase exception properties
public class SsiException : Exception
{
public string Code { get; } // server error code (e.g. "401114"); otherwise the HTTP status or an SDK code
public int StatusCode { get; }
public Dictionary<string, JsonElement>? ResponseBody { get; } // body with secrets masked
public IReadOnlyDictionary<string, string> Headers { get; } // response headers (rate limit, Retry-After...), never Authorization
}RateLimitException adds:
public long? RetryAfterMs { get; } // milliseconds the server asked to wait (Retry-After), when sent
public int? Limit { get; }
public int? Remaining { get; }
public double? Reset { get; }TradingWsError adds int Status, string ErrorCode, string ServerMessage. Its message never contains signed params.
Server error codes: ServerErrorCode and HttpStatus
ServerErrorCode is a static class holding the numeric codes the server returns in the code field (for example PushApprovalPending = 401114, SignatureMissing = 401006, SignatureBodyEmpty = 401008, SignatureMismatch = 401013, ReplayWindowExceeded = 400111, OtpVerificationFailed = 401100, NoScopeGranted = 403100), plus ServerErrorCode.IsKnown(int code). For the full list see the SDK source and Error Codes. HttpStatus has the classification helpers IsAuthError, IsRateLimit, IsClientError, IsServerError.
Basic usage
using SsiSdk;
try
{
await auth.AuthenticateAsync("YOUR_OTP");
}
catch (SsiException ex)
{
Console.WriteLine($"Error: {ex.Message} (code: {ex.Code})");
}Handling by error type
Controls applied: the account is a placeholder,
catchblocks go from specific to general, no automatic retry for orders (the SDK never resends POST), and neitherResponseBodynor tokens are printed to logs.
using SsiSdk;
try
{
var result = await trading.Trading.PlaceLimitOrderAsync(
"YOUR_ACCOUNT_NO", "SSI", OrderSide.Buy, 100, 68000m);
}
catch (SmartOtpRejectedException)
{
Console.WriteLine("Smart OTP was rejected, request a new OTP");
}
catch (SmartOtpPendingException)
{
Console.WriteLine("Smart OTP not approved yet");
}
catch (ReauthenticationRequiredException)
{
Console.WriteLine("Token expired, authenticate again");
}
catch (AuthenticationException)
{
Console.WriteLine("Re-authentication required");
}
catch (RateLimitException ex)
{
Console.WriteLine($"Rate limited, retry after {ex.RetryAfterMs} ms");
}
catch (DuplicateRequestException ex)
{
// Must be caught before ApiException
Console.WriteLine($"Duplicate request (409): {ex.Message}");
}
catch (ApiException ex)
{
Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
}
catch (ValidationException ex)
{
Console.WriteLine($"Validation: {ex.Message}");
}
catch (TradingWsError ex)
{
Console.WriteLine($"Trading WS rejected: {ex.Status} {ex.ErrorCode}");
}
catch (WebSocketException ex)
{
Console.WriteLine($"WebSocket: {ex.Message}");
}
catch (SsiException ex)
{
Console.WriteLine($"SDK error: {ex.Message}");
}The example above mixes REST and WebSocket errors to show the catch order; in practice a single call throws only some of them (TradingWsError comes only from the Trading WebSocket).
Handling tips
- Catch specific exceptions first and
SsiExceptionlast as a fallback (ordercatchblocks from specific to general).SmartOtp*/ReauthenticationRequiredExceptionmust come beforeAuthenticationException,DuplicateRequestExceptionbeforeApiException,TradingWsErrorbeforeWebSocketException. - Catch
AuthenticationExceptionwhen you need to refresh the token or request a new OTP. With Smart OTP,SmartOtpPendingExceptionmeans not approved yet (you may try again), whileSmartOtpRejectedExceptionis terminal: ask for a new OTP. - Catch
RateLimitExceptionand respectRetryAfterMs(milliseconds) to back off. The SDK waits out a 429 only for GET; POST/PUT/DELETE are never resent. - Catch
ApiExceptionto handle business errors from the server (Code,StatusCode,ResponseBody). - Catch
WebSocketExceptionwhen handling streaming. After a command timeout the outcome is unknown: check the order book before resending. - Catch
ValidationExceptionfor invalid input before calling the API (the SDK validates on the client; no request is sent).