Error Handling
Exception hierarchy and error handling patterns in Python SDK
The SDK uses a hierarchical exception system:
Exception List
| Exception | Description |
|---|---|
SSIError | Base exception — has message, code, status_code, response_body, headers |
AuthenticationError | Authentication failed (wrong credentials, expired token) |
SmartOTPPendingError | Child 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 |
SmartOTPRejectedError | Child of AuthenticationError. The Smart OTP was rejected, expired or the transaction was not found (401113, 401115, 401116). The SDK stops polling immediately |
ReauthenticationRequired | Child 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 |
APIError | API returned an error — code is the server error code (or the HTTP status as a string), plus status_code and response_body |
DuplicateRequestError | Child 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 |
WebSocketError | WebSocket connection or communication error |
TradingWSError | Child of WebSocketError. An error frame from the Trading WebSocket — has status, error_code, server_message. See Trading WebSocket |
ValidationError | Input validation error (checked in the SDK, before the request is sent) |
RateLimitError | Request 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 → RateLimitErrorBasic 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
apiKeyhas only 1 active session. When a refresh token is reused, the server revokes all sessions of thatapiKey(401103). ReauthenticationRequiredis never retried automatically.
RateLimitError
retry_after_msis in milliseconds (the SDK converts theRetry-Afterheader, which is in seconds).limitandremainingcome from theX-RateLimit-Limit/X-RateLimit-Remainingheaders when the server sends them.resetis usuallyNonebecause 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
SSIErrorat the outermost layer to collect general errors. - Catch
AuthenticationErrorwhen 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
RateLimitErrorand respectretry_after_msfor backoff. - Catch
APIErrorto handle business errors from the server. An HTTP 200 carrying acodeother than 200/204 is also raised asAPIError. - 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
WebSocketErrorwhen handling streaming; the Trading WebSocket raisesTradingWSError. - Catch
ValidationErrorwhen you need to validate input before calling the API.