Go 1.21+

Go Integration

Strongly-typed API access with Go's concurrency primitives for parallel multi-symbol fetching. Zero external dependencies — uses only the standard library.

~10 min setupREST + JSONX-API-Key authentication
~10 minTypical setup
Programming LanguagesIntegration type
REST + JSONUniversal interface
X-API-KeyOne auth pattern

Struct Definitions

Strongly-typed structs for all API responses:

tickatlas/types.go
package tickatlas

import "time"

// Every success response is {"success": true, "data": {...}}; the client
// below unwraps the envelope, so these structs describe the "data" block.

// IndicatorResponse represents a single indicator value (GET /v1/indicator).
type IndicatorResponse struct {
    Symbol     string  `json:"symbol"`
    Timeframe  string  `json:"timeframe"`
    Indicator  string  `json:"indicator"`
    Value      float64 `json:"value"`
    Bid        float64 `json:"bid"`
    Ask        float64 `json:"ask"`
    UpdatedAt  string  `json:"updated_at"`
    ServerTime string  `json:"server_time"`
}

// QuoteResponse represents a live price quote (GET /v1/quote).
type QuoteResponse struct {
    Symbol    string    `json:"symbol"`
    Bid       float64   `json:"bid"`
    Ask       float64   `json:"ask"`
    Spread    float64   `json:"spread"`
    Timestamp time.Time `json:"timestamp"`
}

// SummaryResponse represents a rule-based market summary (GET /v1/summary).
// Summary is the human-readable text.
type SummaryResponse struct {
    Symbol         string   `json:"symbol"`
    Timeframe      string   `json:"timeframe"`
    Bias           string   `json:"bias"`
    Confidence     float64  `json:"confidence"`
    TrendScore     float64  `json:"trend_score"`
    MomentumScore  float64  `json:"momentum_score"`
    BullishSignals []string `json:"bullish_signals"`
    BearishSignals []string `json:"bearish_signals"`
    Summary        string   `json:"summary"`
}

// ScreenerResult represents one symbol in a screener scan.
type ScreenerResult struct {
    Symbol string  `json:"symbol"`
    Value  float64 `json:"value"`
    Bid    float64 `json:"bid"`
}

type ScreenerResponse struct {
    Indicator    string           `json:"indicator"`
    Timeframe    string           `json:"timeframe"`
    Results      []ScreenerResult `json:"results"`
    TotalMatches int              `json:"total_matches"`
}

API Client

A clean client using net/http with proper error handling:

tickatlas/client.go
package tickatlas

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "time"
)

const baseURL = "https://tickatlas.com/v1"

// Client is the TickAtlas API client.
type Client struct {
    apiKey     string
    httpClient *http.Client
}

// NewClient creates a new TickAtlas client.
func NewClient(apiKey string) *Client {
    return &Client{
        apiKey: apiKey,
        httpClient: &http.Client{
            Timeout: 10 * time.Second,
        },
    }
}

func (c *Client) get(endpoint string, params url.Values) ([]byte, error) {
    reqURL := fmt.Sprintf("%s/%s?%s", baseURL, endpoint, params.Encode())
    req, err := http.NewRequest("GET", reqURL, nil)
    if err != nil {
        return nil, err
    }
    req.Header.Set("X-API-Key", c.apiKey)

    resp, err := c.httpClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    if resp.StatusCode == 429 {
        return nil, fmt.Errorf("rate limited: retry after %s", resp.Header.Get("Retry-After"))
    }
    if resp.StatusCode != 200 {
        return nil, fmt.Errorf("API error: %d %s", resp.StatusCode, resp.Status)
    }

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }

    // Unwrap the {"success": true, "data": {...}} envelope once, here.
    var env struct {
        Success bool            `json:"success"`
        Data    json.RawMessage `json:"data"`
    }
    if err := json.Unmarshal(body, &env); err != nil {
        return nil, err
    }
    return env.Data, nil
}

// Indicator fetches a single indicator value.
func (c *Client) Indicator(symbol, indicator, timeframe string) (*IndicatorResponse, error) {
    params := url.Values{
        "symbol": {symbol}, "indicator": {indicator}, "timeframe": {timeframe},
    }
    body, err := c.get("indicator", params)
    if err != nil {
        return nil, err
    }
    var result IndicatorResponse
    return &result, json.Unmarshal(body, &result)
}

// Quote fetches a live price quote.
func (c *Client) Quote(symbol string) (*QuoteResponse, error) {
    body, err := c.get("quote", url.Values{"symbol": {symbol}})
    if err != nil {
        return nil, err
    }
    var result QuoteResponse
    return &result, json.Unmarshal(body, &result)
}

// Summary fetches the Market Summary.
func (c *Client) Summary(symbol, timeframe string) (*SummaryResponse, error) {
    body, err := c.get("summary", url.Values{"symbol": {symbol}, "timeframe": {timeframe}})
    if err != nil {
        return nil, err
    }
    var result SummaryResponse
    return &result, json.Unmarshal(body, &result)
}

// Screener scans symbols by indicator criteria.
func (c *Client) Screener(indicator, timeframe string, maxVal float64) (*ScreenerResponse, error) {
    params := url.Values{
        "indicator": {indicator}, "timeframe": {timeframe},
        "max_val": {fmt.Sprintf("%.2f", maxVal)},
    }
    body, err := c.get("screener", params)
    if err != nil {
        return nil, err
    }
    var result ScreenerResponse
    return &result, json.Unmarshal(body, &result)
}

Concurrent Multi-Symbol Fetching

Use goroutines and channels to fetch indicators for multiple symbols simultaneously:

main.go
package main

import (
    "fmt"
    "os"
    "sync"
)

// classify turns an RSI value into a label. This is your rule — the API
// returns the value, it does not return a signal field.
func classify(rsi float64) string {
    switch {
    case rsi < 30:
        return "oversold"
    case rsi > 70:
        return "overbought"
    default:
        return "neutral"
    }
}

func main() {
    client := tickatlas.NewClient(os.Getenv("CLAW_API_KEY"))

    symbols := []string{"EURUSD", "GBPUSD", "USDJPY", "XAUUSD", "BTCUSD"}

    var wg sync.WaitGroup
    type result struct {
        Symbol string
        RSI    float64
        Signal string
        Err    error
    }
    results := make(chan result, len(symbols))

    // Fetch RSI for all symbols concurrently
    for _, sym := range symbols {
        wg.Add(1)
        go func(s string) {
            defer wg.Done()
            ind, err := client.Indicator(s, "RSI_14", "H1")
            if err != nil {
                results <- result{Symbol: s, Err: err}
                return
            }
            results <- result{Symbol: s, RSI: ind.Value, Signal: classify(ind.Value)}
        }(sym)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    fmt.Println("Symbol    | RSI     | Signal")
    fmt.Println("----------|---------|--------")
    for r := range results {
        if r.Err != nil {
            fmt.Printf("%-9s | ERROR   | %v\n", r.Symbol, r.Err)
        } else {
            fmt.Printf("%-9s | %6.2f  | %s\n", r.Symbol, r.RSI, r.Signal)
        }
    }
}

Error Handling with Retry

go
// Robust error handling pattern
func fetchWithRetry(client *tickatlas.Client, symbol, indicator, tf string) (*tickatlas.IndicatorResponse, error) {
    maxRetries := 3
    for i := 0; i < maxRetries; i++ {
        result, err := client.Indicator(symbol, indicator, tf)
        if err == nil {
            return result, nil
        }

        // Don't retry on auth errors
        if strings.Contains(err.Error(), "401") || strings.Contains(err.Error(), "403") {
            return nil, fmt.Errorf("auth error (not retryable): %w", err)
        }

        // Exponential backoff
        wait := time.Duration(1<<uint(i)) * time.Second
        log.Printf("Attempt %d/%d failed: %v. Retrying in %v...", i+1, maxRetries, err, wait)
        time.Sleep(wait)
    }
    return nil, fmt.Errorf("failed after %d retries", maxRetries)
}