OKX Golang SDK

August 29, 2026 ยท View on GitHub

OKX Golang client

CI Tests Go vet Go Version License CodeQL codecov Go Report Card

Go client for the OKX v5 API. Covers 335 REST endpoints and 53 WebSocket channels.

Package: pkg.go.dev/github.com/tigusigalpa/okx-go

๐Ÿ“– Full documentation available on Wiki

Install

go get github.com/tigusigalpa/okx-go

Important

Breaking change in v1.1.0: OKXError has been renamed to Error to follow Go naming conventions. Update type assertions and errors.As targets from *okx.OKXError to *okx.Error when upgrading.

What's inside

  • 335 REST endpoints across 16 categories
  • 53 WebSocket channels (public, private, business)
  • Demo trading mode (x-simulated-trading: 1)
  • context.Context everywhere
  • Typed request/response structs
  • WebSocket reconnect with exponential backoff
  • Goroutine-safe
  • Dependencies: stdlib + gorilla/websocket

Quick start

Runnable examples:

go run ./examples/rest
go run ./examples/websocket

REST

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/tigusigalpa/okx-go"
)

func main() {
    client := okx.NewRestClient(
        "your-api-key",
        "your-secret-key",
        "your-passphrase",
        okx.WithDemoTrading(),
    )

    ctx := context.Background()

    balances, err := client.Account.GetBalance(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }

    for _, b := range balances {
        fmt.Printf("Total Equity: %s\n", *b.TotalEq)
    }

    // Place a limit order
    px := "30000"
    order := models.PlaceOrderRequest{
        InstID:  "BTC-USDT",
        TdMode:  "cash",
        Side:    "buy",
        OrdType: "limit",
        Px:      &px,
        Sz:      "0.01",
    }

    result, err := client.Trade.PlaceOrder(ctx, order)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Order ID: %s\n", result[0].OrdID)
}

WebSocket (public)

ws := okx.NewWSClient("", "", "", okx.WSPublicURL)

ctx := context.Background()
if err := ws.Connect(ctx); err != nil {
    log.Fatal(err)
}
defer ws.Close()

ch, err := ws.Subscribe(ctx, "tickers", map[string]interface{}{
    "instId": "BTC-USDT",
})
if err != nil {
    log.Fatal(err)
}

for msg := range ch {
    fmt.Printf("%s\n", msg)
}

WebSocket (private)

ws := okx.NewWSClient(
    "your-api-key",
    "your-secret-key",
    "your-passphrase",
    okx.WSPrivateURL,
)

ctx := context.Background()
if err := ws.Connect(ctx); err != nil {
    log.Fatal(err)
}
defer ws.Close()

if err := ws.Login(ctx); err != nil {
    log.Fatal(err)
}

ch, err := ws.Subscribe(ctx, "account", map[string]interface{}{
    "ccy": "BTC",
})
if err != nil {
    log.Fatal(err)
}

for msg := range ch {
    fmt.Printf("%s\n", msg)
}

Attached TP/SL (attachAlgoOrds)

OKX rejects the legacy inline tpTriggerPx/slTriggerPx fields on POST /api/v5/trade/order for some instruments/scenarios with sCode=54070 ("use the attachAlgoOrds array to place orders via Open API"). Use models.PlaceOrderRequest.AttachAlgoOrds โ€” the current Open API mechanism for attaching TP/SL to the main order (as opposed to a separate, unlinked algo order via PlaceAlgoOrder):

tpTriggerPx := "64000"
tpOrdPx := "-1" // "-1" = execute TP as a market order once triggered
slTriggerPx := "66000"
slOrdPx := "-1" // "-1" = execute SL as a market order once triggered
triggerPxType := "last" // "last", "index", or "mark"

order := models.PlaceOrderRequest{
    InstID:  "BTC-USDT-SWAP",
    TdMode:  "isolated",
    Side:    "sell",
    OrdType: "market",
    Sz:      "1",
    PosSide: strPtr("short"),
    AttachAlgoOrds: []models.AttachAlgoOrderRequest{
        {
            TpTriggerPx:     &tpTriggerPx,
            TpOrdPx:         &tpOrdPx,
            TpTriggerPxType: &triggerPxType,
            SlTriggerPx:     &slTriggerPx,
            SlOrdPx:         &slOrdPx,
            SlTriggerPxType: &triggerPxType,
        },
    },
}

result, err := client.Trade.PlaceOrder(ctx, order)

Notes:

  • AttachAlgoOrderRequest is a request-only DTO (all fields are pointers, so unset fields are omitted from the JSON payload). It is distinct from models.AttachAlgoOrder, which is the response shape returned inside Order.AttachAlgoOrds and has non-optional string fields.
  • Legacy inline TpTriggerPx/TpOrdPx/SlTriggerPx/SlOrdPx fields on PlaceOrderRequest are kept for backward compatibility, but prefer AttachAlgoOrds going forward.

Options

OptionDescriptionDefault
WithHTTPClient(c)Custom *http.Client&http.Client{Timeout: 30s}
WithBaseURL(url)Override base URLhttps://www.okx.com
WithDemoTrading()Demo modeoff
WithTimeout(d)Request timeout30s
WithRateLimiter(true)Rate limiteroff
WithLogger(l)Custom Loggerno-op

REST endpoints

CategoryCountDocs
Account53link
Trade32link
Market Data24link
Public Data24link
Asset26link
Sub-account8link
Trading Bot44link
Copy Trading26link
Block Trading20link
Spread Trading13link
Financial Products33link
Fiat13link
Trading Statistics15link
System1link
Announcement2link
Affiliate1link
Total335

WebSocket channels

Public (31): tickers, candle1D, candle1H, candle30m, trades, books, books5, bbo-tbt, opt-summary, estimated-price, mark-price, mark-price-candle1D, price-limit, open-interest, funding-rate, index-candle30m, index-tickers, status, public-struc-block-trades, block-tickers, block-trades, liquidation-orders, sprd-tickers, sprd-books5, sprd-books-l2-tbt, sprd-public-trades, sprd-candle1D, economic-calendar, call-auction-details, instruments, trades-all

Private (22): account, positions, balance_and_position, orders, orders-algo, algo-advance, liquidation-warning, account-greeks, rfqs, quotes, sprd-orders, sprd-trades, adl-warning, fills, deposit-info, withdrawal-info, grid-orders-spot, grid-orders-contract, grid-positions, grid-sub-orders, algo-recurring-buy, copytrading-lead-notification

Demo trading

REST โ€” pass okx.WithDemoTrading():

client := okx.NewRestClient(apiKey, secret, passphrase, okx.WithDemoTrading())

WebSocket โ€” use demo URLs:

  • okx.WSDemoPublicURL
  • okx.WSDemoPrivateURL
  • okx.WSDemoBusinessURL

Errors

balances, err := client.Account.GetBalance(ctx, nil)
if err != nil {
    if errors.Is(err, okx.ErrUnauthorized) {
        // bad credentials
    } else if errors.Is(err, okx.ErrRateLimited) {
        // slow down
    } else if okxErr, ok := err.(*okx.Error); ok {
        fmt.Printf("code=%s msg=%s\n", okxErr.Code, okxErr.Message)
    }
}

Pagination

OKX uses cursor-based pagination (before/after). There's a generic Paginator[T] helper:

paginator := models.NewPaginator(func(after string) ([]models.Order, string, error) {
    orders, err := client.Trade.GetOrdersHistory(ctx, "SPOT", nil, nil, nil, nil, nil, nil, &after, nil, nil, nil, nil)
    if err != nil {
        return nil, "", err
    }
    var next string
    if len(orders) > 0 {
        next = orders[len(orders)-1].OrdID
    }
    return orders, next, nil
})

allOrders, err := paginator.All()

Tests

# unit
go test ./...

# integration (demo env)
OKX_API_KEY=... OKX_SECRET_KEY=... OKX_PASSPHRASE=... go test -tags=integration ./...

Contributing

Fork, branch, PR. Make sure go test ./... passes and new code has tests. See CONTRIBUTING.md.

License

MIT. See LICENSE.

Author

Igor Sazonov โ€” @tigusigalpa โ€” sovletig@gmail.com

Not affiliated with OKX. Test on demo before going live. Golang library.