Go

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 TypeEmbedsDescription
*ssisdk.SSIError—Base error. Fields: Message, Code (string), StatusCode (int), ResponseBody, Headers
*ssisdk.AuthenticationErrorSSIErrorAuthentication failed (wrong credentials, wrong OTP, expired token)
*ssisdk.SmartOTPPendingErrorAuthenticationErrorSmart OTP approval is still pending (HTTP 202 / code 401114). Poll again with the same transaction id
*ssisdk.SmartOTPRejectedErrorAuthenticationErrorSmart OTP approval was rejected, not found or belongs to another transaction (401113, 401115, 401116)
*ssisdk.ReauthenticationRequiredErrorAuthenticationErrorThe session can no longer be refreshed. A new OTP is needed; the SDK clears the stored token
*ssisdk.APIErrorSSIErrorAPI returned an error — has StatusCode, ResponseBody
*ssisdk.DuplicateRequestErrorAPIErrorHTTP 409: the clientRequestId / batchRequestId was already used today. The SDK never retries it under a new id
*ssisdk.RateLimitErrorSSIErrorRequest rate limit exceeded (HTTP 429, code RATE_LIMITED). Fields: RetryAfterMs *int, Limit *int, Remaining *int, Reset *float64
*ssisdk.WebSocketErrorSSIErrorWebSocket connection or communication error
*ssisdk.TradingWSErrorWebSocketErrorThe server refused or failed a Trading WebSocket command (see below). Fields: Status, ErrorCode, ServerMessage
*ssisdk.ValidationErrorSSIErrorInput 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.

GroupNames and codes
Auth / account / OTPErrCredentialNotLinkedToClient 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
SignatureErrSignatureMissing 401006, ErrSignatureBodyEmpty 401008, ErrSignatureMismatch / ErrSignatureInvalid 401013
TradingErrModifyPriceAndQuantity 400107, ErrReplayWindowExceeded 400111
Market validatorsErrSymbolInvalidCharacters 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):

ErrorWhenWhat to do
*ssisdk.ValidationErrorEmpty batch, over Config.MaxBatchOrders, one order with bad data, ClientRequestID over 20 characters, cancel item with neither or both of OrderID/ClientRequestIDFix the input and send again. No request was sent
*ssisdk.DuplicateRequestErrorHTTP 409: ClientRequestID or batchRequestId already used todayDo not immediately re-place under a new id. Check whether the original order is live
*ssisdk.RateLimitErrorHTTP 429Wait for RetryAfterMs (if present), then resend in a controlled way. The SDK does not auto-resend write requests
*ssisdk.AuthenticationErrorExpired token or missing permission (401/403)Refresh the token or re-authenticate with an OTP
*ssisdk.APIErrorOther server errors, for example 400111 (ErrReplayWindowExceeded) when batchRequestTime is outside the replay windowRead 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.)

ErrorWhenWhat 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.ValidationErrorBad 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.WebSocketErrorNot 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 commandAfter a timeout the command may have been accepted: reconcile through order events or QueryOrderBook before resending
context.DeadlineExceeded / context.Canceledctx expired or was cancelledTreat 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.

ErrorWhenWhat to do
*ssisdk.ValidationErrorUnknown 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 wildcardFix the input. Nothing was sent
*ssisdk.WebSocketErrorListSubscription / UnsubscribeAll timed out waiting for the LIST_SUBSCRIPTION reply; a frame failed to sendCheck IsConnected(), retry once the connection is stable
Error from Connect()Authentication failed (after the token was refreshed), or the server refused the connectionAuthentication 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: DuplicateRequestError before APIError; ReauthenticationRequiredError / SmartOTPPendingError / SmartOTPRejectedError before AuthenticationError; TradingWSError before WebSocketError.
  • Catch *ssisdk.AuthenticationError when a token refresh or new OTP is needed.
  • Catch *ssisdk.RateLimitError and respect RetryAfterMs (check for nil) for backoff.
  • Catch *ssisdk.APIError to handle business errors from the server.
  • Catch *ssisdk.WebSocketError when handling streaming connections.
  • Catch *ssisdk.ValidationError to validate input before API calls.
  • Use *ssisdk.SSIError as a fallback for all SDK errors.
  • Do not log tokens, signatures or OTPs yourself. Token and Config print redacted values through String().

On this page