Error Handling
Error types, server error codes and error handling patterns in Go SDK
The SDK uses a hierarchical error system. All error types implement the error interface. Child types unwrap to their parent, so always use errors.As (not a type switch), and test the more specific type first.
Import
The root package is named ssisdk. Use an explicit alias:
import (
"errors"
ssisdk "github.com/SSI-Securities-Inc/ssi-sdk-go/v3"
)Error Types
| Error Type | Embeds | Description |
|---|---|---|
*ssisdk.SSIError | — | Base error. Fields: Message, Code (string), StatusCode (int), ResponseBody, Headers |
*ssisdk.AuthenticationError | SSIError | Authentication failed (wrong credentials, wrong OTP, expired token) |
*ssisdk.SmartOTPPendingError | AuthenticationError | Smart OTP approval is still pending (HTTP 202 / code 401114). Poll again with the same transaction id |
*ssisdk.SmartOTPRejectedError | AuthenticationError | Smart OTP approval was rejected, not found or belongs to another transaction (401113, 401115, 401116) |
*ssisdk.ReauthenticationRequiredError | AuthenticationError | The session can no longer be refreshed. A new OTP is needed; the SDK clears the stored token |
*ssisdk.APIError | SSIError | API returned an error — has StatusCode, ResponseBody |
*ssisdk.DuplicateRequestError | APIError | HTTP 409: the clientRequestId / batchRequestId was already used today. The SDK never retries it under a new id |
*ssisdk.RateLimitError | SSIError | Request rate limit exceeded (HTTP 429, code RATE_LIMITED). Fields: RetryAfterMs *int, Limit *int, Remaining *int, Reset *float64 |
*ssisdk.WebSocketError | SSIError | WebSocket connection or communication error |
*ssisdk.TradingWSError | WebSocketError | The server refused or failed a Trading WebSocket command (see below). Fields: Status, ErrorCode, ServerMessage |
*ssisdk.ValidationError | SSIError | Input validation error (usually raised locally, nothing was sent) |
RetryAfterMs, Limit and Remaining are pointers: they are nil when the server did not send the value. Reset is always nil for REST.
Basic Usage
token, err := auth.Authenticate(os.Getenv("SSI_OTP"))
if err != nil {
log.Printf("Error: %v", err)
}Detailed Error Handling
result, err := t.Trading.PlaceLimitOrder("YOUR_ACCOUNT_NO", "SSI", trading.OrderSideBuy, 100, 68000)
if err != nil {
var dup *ssisdk.DuplicateRequestError
var rl *ssisdk.RateLimitError
var reauth *ssisdk.ReauthenticationRequiredError
var authErr *ssisdk.AuthenticationError
var apiErr *ssisdk.APIError
var vErr *ssisdk.ValidationError
var wsErr *ssisdk.WebSocketError
switch {
case errors.As(err, &dup):
// 409: do not re-place under a new id before you know whether the first order is live
log.Printf("Duplicate request id: %v", dup)
case errors.As(err, &rl):
if rl.RetryAfterMs != nil {
log.Printf("Rate limited, retry after %d ms", *rl.RetryAfterMs)
} else {
log.Println("Rate limited")
}
case errors.As(err, &reauth):
log.Println("Session expired, a new OTP is needed")
case errors.As(err, &authErr):
log.Println("Authentication error")
case errors.As(err, &apiErr):
log.Printf("API error %d (%s): %s", apiErr.StatusCode, apiErr.Code, apiErr.Message)
case errors.As(err, &vErr):
log.Printf("Validation: %s", vErr.Message)
case errors.As(err, &wsErr):
log.Printf("WebSocket: %s", wsErr.Message)
default:
log.Printf("Unknown error: %v", err)
}
}Because DuplicateRequestError embeds APIError, and ReauthenticationRequiredError, SmartOTPPendingError, SmartOTPRejectedError embed AuthenticationError, the specific case must come before its parent in the switch.
Smart OTP
SmartOTPPendingError is expected while the user has not approved yet. Poll with the same transaction id (EnsureAuthenticated / EnsureAuthenticatedWith poll for you, see Client Classes). SmartOTPRejectedError and ReauthenticationRequiredError are terminal: request a new OTP.
Server Error Codes
ServerErrorCode is a named type for known server codes. The SDK never fails on an unknown code: use ssisdk.LookupServerErrorCode(code) (returns (ServerErrorCode, bool)) or code.IsKnown() to check. Some names alias the same number.
| Group | Names and codes |
|---|---|
| Auth / account / OTP | ErrCredentialNotLinkedToClient 400101, ErrTwoFAInfoError 400102, ErrTwoFAInfoMissing 400103, ErrOTPSendFailed / ErrSymbolRequired 400104, ErrPushApprovalCreateFailed 400171, ErrOTPVerificationFailed 401100, ErrRefreshTokenInvalid 401101, ErrRefreshClientMissing 401102, ErrRefreshTokenRevokedOrExpired 401103, ErrRefreshAPIKeyGone 401104, ErrInvalidAPICredential / ErrTokenWithoutOTP 401105, ErrPushApprovalNotFound 401113, ErrPushApprovalPending / ErrSmartOTPPending 401114, ErrPushApprovalRejected 401115, ErrPushApprovalForeignTransaction 401116, ErrNoScopeGranted 403100 |
| Signature | ErrSignatureMissing 401006, ErrSignatureBodyEmpty 401008, ErrSignatureMismatch / ErrSignatureInvalid 401013 |
| Trading | ErrModifyPriceAndQuantity 400107, ErrReplayWindowExceeded 400111 |
| Market validators | ErrSymbolInvalidCharacters 400006, ErrFromAfterTo 400202, ErrBoardNotAllowed 400208, ErrTimeframeNotSupported 400210, ErrDateFormatInvalid 400213, ErrIndexSummaryNeedsOneOf 504207, ErrSecuritiesInfoNeedsOneOf 504208, ErrSecuritiesSummaryNeedsSymbolXorIndex 504209 |
Codes 401101-401104 and 401106-401108 make the SDK stop refreshing and ask for re-authentication (ReauthenticationRequiredError).
Batch Order Errors
Errors of PlaceBatchOrders / CancelBatchOrders (details in Batch Orders):
| Error | When | What to do |
|---|---|---|
*ssisdk.ValidationError | Empty batch, over Config.MaxBatchOrders, one order with bad data, ClientRequestID over 20 characters, cancel item with neither or both of OrderID/ClientRequestID | Fix the input and send again. No request was sent |
*ssisdk.DuplicateRequestError | HTTP 409: ClientRequestID or batchRequestId already used today | Do not immediately re-place under a new id. Check whether the original order is live |
*ssisdk.RateLimitError | HTTP 429 | Wait for RetryAfterMs (if present), then resend in a controlled way. The SDK does not auto-resend write requests |
*ssisdk.AuthenticationError | Expired token or missing permission (401/403) | Refresh the token or re-authenticate with an OTP |
*ssisdk.APIError | Other server errors, for example 400111 (ErrReplayWindowExceeded) when batchRequestTime is outside the replay window | Read Code, StatusCode, Message. Keep the machine clock accurate (NTP) |
On a timeout or a dropped connection you cannot know whether the batch reached the system, because write requests are not retried. Reconcile the order status first (for example through order events on streaming). If you must resend, reuse the same ClientRequestID values so the 409 duplicate protection still applies.
A per-order Success == false in BatchOrderResult does not make the call return an error; it is that order's own failure (ErrorCode / ErrorMessage).
Trading WebSocket Errors
See Trading WebSocket.
func reportWSError(op string, err error) {
var wsErr *ssisdk.TradingWSError
var vErr *ssisdk.ValidationError
var connErr *ssisdk.WebSocketError
switch {
case errors.As(err, &wsErr):
// The server refused: Status, ErrorCode, ServerMessage.
fmt.Printf("%s: server refused status=%d code=%s: %s\n",
op, wsErr.Status, wsErr.ErrorCode, wsErr.ServerMessage)
case errors.As(err, &vErr):
fmt.Printf("%s: invalid input (nothing was sent): %v\n", op, vErr)
case errors.Is(err, context.DeadlineExceeded), errors.Is(err, context.Canceled):
fmt.Printf("%s: context ended: %v\n", op, err)
case errors.As(err, &connErr):
fmt.Printf("%s: websocket error: %v\n", op, connErr)
default:
fmt.Printf("%s: error: %v\n", op, err)
}
}(imports context, errors, fmt and the ssisdk alias.)
| Error | When | What to do |
|---|---|---|
*ssisdk.TradingWSError (Status, ErrorCode, ServerMessage) | A server error frame (status 4xx or "error", for example FCO_ERROR for fco.*), or a post-upgrade refusal frame (401 token, 403 missing tradingws:* scope, 429, 500) | Read Status and ErrorCode. 401: re-authenticate. 403: check scopes. 429: rate limit, wait according to the quota (Client().RateLimit()) and resend in a controlled way. 409 from a reused clientRequestID: do not re-place under a new id until you know whether the original order is live |
*ssisdk.ValidationError | Bad input (empty account or wildcard, clientRequestID over 20 characters, invalid signature encoding, params that are not valid JSON, ...) | Fix the input. Nothing was sent |
*ssisdk.WebSocketError | Not connected, send failure, connection lost before a response, timeout waiting for a response (not retried), a URL that is not wss://, missing private key for a state-changing command | After a timeout the command may have been accepted: reconcile through order events or QueryOrderBook before resending |
context.DeadlineExceeded / context.Canceled | ctx expired or was cancelled | Treat like a timeout: do not assume the command was not accepted |
The WebSocket client does not return *RateLimitError (that type is REST-only); a rate limit appears as TradingWSError with Status 429. TradingWSError never carries signed params. A refused signature (401006, 401008, 401013) in auto mode is re-sent once with the other encoding; if it still fails, check Config.PrivateKey.
Streaming Errors
Errors after the stream connection is up (network drop, token expiry) are not returned by the subscribe methods. Watch them through SetOnConnection (State, Err) and LastError(). When State == "failed", the SDK stops reconnecting and you must authenticate again. See Connection events.
| Error | When | What to do |
|---|---|---|
*ssisdk.ValidationError | Unknown topic type or invalid filter, empty list, interval other than tick / 1m / 5m, interval on a topic that does not support it, interval on an index with a wildcard | Fix the input. Nothing was sent |
*ssisdk.WebSocketError | ListSubscription / UnsubscribeAll timed out waiting for the LIST_SUBSCRIPTION reply; a frame failed to send | Check IsConnected(), retry once the connection is stable |
Error from Connect() | Authentication failed (after the token was refreshed), or the server refused the connection | Authentication error: Authenticate again with a new OTP. 429: wait, and check how many sockets are open (at most 10 per client) |
Handling Guidance
- Use
errors.As(not a type switch) and test specific types first:DuplicateRequestErrorbeforeAPIError;ReauthenticationRequiredError/SmartOTPPendingError/SmartOTPRejectedErrorbeforeAuthenticationError;TradingWSErrorbeforeWebSocketError. - Catch
*ssisdk.AuthenticationErrorwhen a token refresh or new OTP is needed. - Catch
*ssisdk.RateLimitErrorand respectRetryAfterMs(check fornil) for backoff. - Catch
*ssisdk.APIErrorto handle business errors from the server. - Catch
*ssisdk.WebSocketErrorwhen handling streaming connections. - Catch
*ssisdk.ValidationErrorto validate input before API calls. - Use
*ssisdk.SSIErroras a fallback for all SDK errors. - Do not log tokens, signatures or OTPs yourself.
TokenandConfigprint redacted values throughString().