Skip to main content

Overview

The SDK provides a typed error hierarchy, a generic retry function with exponential backoff, a RetryableClient wrapper, and pluggable logging — all following idiomatic Go patterns.

Error Types

All API errors implement the error interface and can be inspected using errors.As():
import "errors"

_, err := marketFetcher.GetMarket(ctx, "invalid-slug")
if err != nil {
    var apiErr *limitless.APIError
    if errors.As(err, &apiErr) {
        fmt.Printf("Status: %d\n", apiErr.Status)
        fmt.Printf("Message: %s\n", apiErr.Message)
        fmt.Printf("URL: %s %s\n", apiErr.Method, apiErr.URL)
    }
}

APIError

The base error type for all HTTP errors. Its Error() method returns a formatted string like "API error 401 GET /path: message".
FieldTypeDescription
StatusintHTTP status code (e.g. 400, 401, 429, 500)
MessagestringHuman-readable error message from the API
Datajson.RawMessageRaw response body
URLstringRequest URL path
MethodstringHTTP method

Specialized Error Types

The SDK provides three specialized error types that embed APIError:
TypeHTTP StatusDescription
*ValidationError400Bad request — invalid parameters or malformed payload
*AuthenticationError401, 403Missing/invalid API key or insufficient permissions
*RateLimitError429Rate limit exceeded
var rateLimitErr *limitless.RateLimitError
if errors.As(err, &rateLimitErr) {
    fmt.Println("Rate limited! Back off and retry.")
}

var authErr *limitless.AuthenticationError
if errors.As(err, &authErr) {
    fmt.Println("Check your API key.")
}

var validErr *limitless.ValidationError
if errors.As(err, &validErr) {
    fmt.Println("Invalid request:", validErr.Message)
}

OrderValidationError

Client-side validation errors thrown before a request is made:
var orderErr *limitless.OrderValidationError
if errors.As(err, &orderErr) {
    fmt.Printf("Field %s: %s\n", orderErr.Field, orderErr.Message)
}
FieldTypeDescription
FieldstringThe field that failed validation (e.g. "price", "tokenId")
MessagestringDescription of the validation failure

Common Status Codes

CodeMeaningTypical Cause
400Bad RequestInvalid order parameters, malformed payload
401UnauthorizedMissing or invalid API key
403ForbiddenInsufficient permissions or geographic restriction
404Not FoundInvalid market slug or order ID
429Too Many RequestsRate limit exceeded
500Internal Server ErrorTransient server-side failure

Helper Method

APIError provides an IsAuthError() method to quickly check for authentication issues:
if apiErr.IsAuthError() {
    fmt.Println("Authentication problem — check your API key")
}

WithRetry (Generic)

The SDK provides a generic WithRetry function that wraps any operation with configurable retry logic using Go generics:
market, err := limitless.WithRetry(ctx, func() (*limitless.Market, error) {
    return marketFetcher.GetMarket(ctx, "btc-above-100k-march-2025")
}, limitless.RetryConfig{
    StatusCodes:     []int{429, 500, 502, 503, 504},
    MaxRetries:      3,
    ExponentialBase: 2.0,
    MaxDelay:        60 * time.Second,
    OnRetry: func(attempt int, err error, delay time.Duration) {
        fmt.Printf("Retry %d after %s: %v\n", attempt, delay, err)
    },
})

RetryConfig

FieldTypeDefaultDescription
StatusCodes[]int[429, 500, 502, 503, 504]HTTP status codes that trigger a retry
MaxRetriesint3Maximum number of retry attempts
Delays[]time.DurationnilFixed delays per attempt (overrides exponential backoff)
ExponentialBasefloat642.0Base for exponential backoff calculation
MaxDelaytime.Duration60sMaximum delay between retries
OnRetryfunc(int, error, time.Duration)nilCallback invoked on each retry
WithRetry only retries when the error is an *APIError whose Status is in the StatusCodes list. All other errors propagate immediately.

Fixed Delays

Override exponential backoff with explicit per-attempt delays:
config := limitless.RetryConfig{
    StatusCodes: []int{429, 500},
    MaxRetries:  3,
    Delays:      []time.Duration{1 * time.Second, 2 * time.Second, 4 * time.Second},
}

RetryableClient

For broader retry coverage, wrap your HttpClient with RetryableClient. This applies retry logic to all API calls made through the client:
client := limitless.NewHttpClient()
retryable := limitless.NewRetryableClient(client, limitless.RetryConfig{
    StatusCodes:     []int{429, 500, 502, 503, 504},
    MaxRetries:      3,
    ExponentialBase: 2.0,
})

// Use retryable in place of client
marketFetcher := limitless.NewMarketFetcher(retryable.Client)
The RetryableClient has the same methods as HttpClient (Get, GetRaw, Post, Delete) and transparently retries on configured status codes.
Use RetryableClient for automated trading bots that need resilience against transient failures without wrapping every call in WithRetry individually.

Debugging with ConsoleLogger

Enable verbose logging to trace requests, responses, and internal operations:
logger := limitless.NewConsoleLogger(limitless.LogLevelDebug)
client := limitless.NewHttpClient(limitless.WithLogger(logger))
At LogLevelDebug, the logger outputs:
  • Request and response headers
  • Venue cache hits and misses
  • Full API response bodies
  • Retry attempts and delays
  • WebSocket connection state changes
[DEBUG] GET /markets/btc-above-100k-march-2025
[DEBUG] Headers: {X-API-Key: lmts_***, Content-Type: application/json}
[DEBUG] Response 200 in 142ms
[DEBUG] Venue cached for btc-above-100k-march-2025: exchange=0xA1b2... adapter=0xD4e5...
[DEBUG] POST /orders
[DEBUG] Response 429 in 38ms
[DEBUG] Retry 1/3 after 1s — status 429
[DEBUG] POST /orders
[DEBUG] Response 200 in 156ms

Custom Logger

Implement the Logger interface to plug in your own logging backend:
type Logger interface {
    Debug(msg string, meta ...map[string]any)
    Info(msg string, meta ...map[string]any)
    Warn(msg string, meta ...map[string]any)
    Error(msg string, err error, meta ...map[string]any)
}
The Error method has an additional err error parameter compared to the other methods, allowing structured error logging.

Best Practices

1

Always check errors

Every SDK method returns error as the last return value. Always check it:
result, err := orderClient.CreateOrder(ctx, params)
if err != nil {
    var apiErr *limitless.APIError
    if errors.As(err, &apiErr) {
        log.Printf("API error %d: %s", apiErr.Status, apiErr.Message)
    }
    return err
}
2

Use typed error assertions

Use errors.As() to match specific error types and handle them differently:
var rateLimitErr *limitless.RateLimitError
if errors.As(err, &rateLimitErr) {
    // Back off and retry
}
var authErr *limitless.AuthenticationError
if errors.As(err, &authErr) {
    // Check API key configuration
}
3

Retry only transient errors

Limit retries to status codes like 429 (rate limit) and 5xx (server errors). Do not retry 400 (bad request) or 401 (auth failure) as these require corrective action.
4

Use exponential backoff

The default RetryConfig uses exponential backoff with a base of 2.0. Avoid fixed short delays that could overwhelm the API during outages.
5

Pass context for cancellation

Always pass a context.Context to allow timeouts and cancellation:
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

market, err := marketFetcher.GetMarket(ctx, slug)
6

Use DEBUG logging during development

Enable LogLevelDebug to see the full request/response cycle. Switch to LogLevelInfo or LogLevelWarn in production to reduce noise.