Python

Error Handling

Exception hierarchy and error handling patterns in Python SDK

The SDK uses a hierarchical exception system:

Exception List

ExceptionDescription
SSIErrorBase exception — has message, code, status_code, response_body, headers
AuthenticationErrorAuthentication failed (wrong credentials, expired token)
SmartOTPPendingErrorChild of AuthenticationError. The Smart OTP has not been approved on the device yet (HTTP 202 / 401114), or polling timed out. Call ensure_authenticated(transaction_id=...) again with the same transaction_id
SmartOTPRejectedErrorChild of AuthenticationError. The Smart OTP was rejected, expired or the transaction was not found (401113, 401115, 401116). The SDK stops polling immediately
ReauthenticationRequiredChild of AuthenticationError. The token cannot be refreshed, or the server rejected the refresh (401101, 401103). The SDK clears the stored token and does not retry; authenticate again with a new OTP
APIErrorAPI returned an error — code is the server error code (or the HTTP status as a string), plus status_code and response_body
DuplicateRequestErrorChild of APIError. HTTP 409: clientRequestId / batchRequestId was already used today. The SDK never re-sends the order under a new id because the original order may be live
WebSocketErrorWebSocket connection or communication error
TradingWSErrorChild of WebSocketError. An error frame from the Trading WebSocket — has status, error_code, server_message. See Trading WebSocket
ValidationErrorInput validation error (checked in the SDK, before the request is sent)
RateLimitErrorRequest rate limit exceeded — has status_code, retry_after_ms, limit, remaining, reset

Inheritance Chain

Exception → SSIError → AuthenticationError → SmartOTPPendingError
                                           → SmartOTPRejectedError
                                           → ReauthenticationRequired
Exception → SSIError → APIError → DuplicateRequestError
Exception → SSIError → WebSocketError → TradingWSError
Exception → SSIError → ValidationError
Exception → SSIError → RateLimitError

Basic Usage

from ssi_sdk import SSIError

try:
    auth.authenticate(otp="<OTP>")
except SSIError as e:
    print(f"Error: {e.message} (code: {e.code})")

Detailed Error Handling

from ssi_sdk.exceptions import (
    APIError,
    AuthenticationError,
    DuplicateRequestError,
    RateLimitError,
)
from ssi_sdk.enums import OrderSide

try:
    result = trading.trading.place_limit_order(
        account_no="<ACCOUNT_NO>",
        symbol="SSI",
        side=OrderSide.BUY,
        quantity=100,
        price=68000,
    )
except AuthenticationError:
    print("Re-authentication required")
except RateLimitError as e:
    print(f"Rate limited, retry in {e.retry_after_ms} ms")
except DuplicateRequestError:
    print("client_request_id was already used today, do not resend")
except APIError as e:
    print(f"API error {e.status_code} (code {e.code}): {e.message}")

DuplicateRequestError is a child of APIError, so catch it before APIError.

Smart OTP and Re-authentication

from ssi_sdk.exceptions import (
    ReauthenticationRequired,
    SmartOTPPendingError,
    SmartOTPRejectedError,
)

otp = auth.request_otp_typed()          # OTPResponse(message, transaction_id)
try:
    auth.ensure_authenticated(transaction_id=otp.transaction_id)
except SmartOTPPendingError:
    # Not approved in time: call again with the same transaction_id
    auth.ensure_authenticated(transaction_id=otp.transaction_id)
except SmartOTPRejectedError:
    print("Smart OTP rejected or expired, request a new OTP")
except ReauthenticationRequired:
    print("Session cannot be refreshed, authenticate again with a new OTP")
  • Each apiKey has only 1 active session. When a refresh token is reused, the server revokes all sessions of that apiKey (401103).
  • ReauthenticationRequired is never retried automatically.

RateLimitError

  • retry_after_ms is in milliseconds (the SDK converts the Retry-After header, which is in seconds).
  • limit and remaining come from the X-RateLimit-Limit / X-RateLimit-Remaining headers when the server sends them.
  • reset is usually None because the server does not send a reset time.

ServerErrorCode

ssi_sdk.enums.ServerErrorCode lists the application error codes the server returns in {"code", "msg"} (for example OTP_VERIFICATION_FAILED = 401100, REFRESH_TOKEN_REVOKED_OR_EXPIRED = 401103, PUSH_APPROVAL_REJECTED = 401115, MODIFY_PRICE_AND_QUANTITY = 400107, REPLAY_WINDOW_EXCEEDED = 400111, TIMEFRAME_NOT_SUPPORTED = 400210). Some numbers are reused with different meanings (for example 400104 is both "OTP could not be sent" and "symbol is required"), so the later name is an alias of the first. The signature codes (4010xx) do not fully line up with the server's own error-code-mapping.json: trust the number, not the mapped text.

from ssi_sdk.enums import ServerErrorCode

try:
    ...
except APIError as e:
    if ServerErrorCode.from_value(e.code) == ServerErrorCode.REPLAY_WINDOW_EXCEEDED:
        print("Request is outside the allowed time window")

from_value returns None for a code that is not in the enum, so it never raises. HTTPStatus (same module) is an IntEnum of the HTTP statuses the API uses (with is_auth_error, is_rate_limit, is_client_error, is_server_error).

Handling Guidance

  • Catch SSIError at the outermost layer to collect general errors.
  • Catch AuthenticationError when you need to refresh the token or request a new OTP; catch its children (SmartOTPPendingError, SmartOTPRejectedError, ReauthenticationRequired) first if you need to handle them separately.
  • Catch RateLimitError and respect retry_after_ms for backoff.
  • Catch APIError to handle business errors from the server. An HTTP 200 carrying a code other than 200/204 is also raised as APIError.
  • Only GET requests are retried by the SDK; POST/PUT/DELETE (place order, cancel order, batch) are never retried automatically.
  • Per-order errors in a batch order are data in BatchOrderResult, not exceptions. See Batch Order.
  • Catch WebSocketError when handling streaming; the Trading WebSocket raises TradingWSError.
  • Catch ValidationError when you need to validate input before calling the API.

On this page