An official Go SDK is coming soon. In the meantime, this guide shows how to use TheRundown API directly with the standard
net/http package and gorilla/websocket for WebSocket connections.Installation
No external dependencies are required for REST calls. For WebSocket support:Copy
go get github.com/gorilla/websocket
Configuration
Copy
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"time"
)
const (
baseURL = "https://therundown.io/api/v2"
wsURL = "wss://therundown.io/api/v2/ws/markets"
)
var apiKey = os.Getenv("THERUNDOWN_API_KEY")
Helper Function
Copy
func apiGet(path string, params map[string]string) ([]byte, error) {
u, err := url.Parse(baseURL + path)
if err != nil {
return nil, err
}
q := u.Query()
q.Set("key", apiKey)
for k, v := range params {
q.Set(k, v)
}
u.RawQuery = q.Encode()
resp, err := http.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
return nil, fmt.Errorf("rate limited, retry after %s", resp.Header.Get("X-RateLimit-Reset"))
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %d %s", resp.StatusCode, resp.Status)
}
return io.ReadAll(resp.Body)
}
Getting Sports
Copy
type Sport struct {
SportID int `json:"sport_id"`
SportName string `json:"sport_name"`
}
type SportsResponse struct {
Sports []Sport `json:"sports"`
}
func getSports() ([]Sport, error) {
body, err := apiGet("/sports", nil)
if err != nil {
return nil, err
}
var resp SportsResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}
return resp.Sports, nil
}
func main() {
sports, err := getSports()
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, s := range sports {
fmt.Printf("%d: %s\n", s.SportID, s.SportName)
}
}
Getting Events with Odds
Copy
type Price struct {
PriceValue float64 `json:"price"`
AffiliateID int `json:"affiliate_id"`
}
type Line struct {
LineValue float64 `json:"line"`
IsMain bool `json:"is_main"`
Prices map[string]Price `json:"prices"`
}
type Participant struct {
ParticipantID int `json:"participant_id"`
Name string `json:"name"`
ParticipantType string `json:"participant_type,omitempty"`
Lines []Line `json:"lines"`
}
type Market struct {
MarketID int `json:"market_id"`
Name string `json:"name"`
PeriodID int `json:"period_id"`
Participants []Participant `json:"participants"`
}
type Team struct {
TeamID int `json:"team_id"`
Name string `json:"name"`
}
type Event struct {
EventID string `json:"event_id"`
SportID int `json:"sport_id"`
Teams []Team `json:"teams"`
Markets []Market `json:"markets"`
}
type EventsResponse struct {
Events []Event `json:"events"`
}
func getEvents(sportID int, date string) ([]Event, error) {
path := fmt.Sprintf("/sports/%d/events/%s", sportID, date)
body, err := apiGet(path, map[string]string{
"market_ids": "1,2,3",
"affiliate_ids": "19,23",
"main_line": "true",
})
if err != nil {
return nil, err
}
var resp EventsResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, err
}
return resp.Events, nil
}
func formatPrice(p float64) string {
if p == 0.0001 {
return "N/A"
}
if p > 0 {
return fmt.Sprintf("+%d", int(p))
}
return fmt.Sprintf("%d", int(p))
}
func main() {
today := time.Now().Format("2006-01-02")
events, err := getEvents(4, today)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
for _, event := range events {
away := event.Teams[0].Name
home := event.Teams[1].Name
fmt.Printf("\n%s @ %s\n", away, home)
for _, market := range event.Markets {
fmt.Printf(" %s:\n", market.Name)
for _, p := range market.Participants {
for _, line := range p.Lines {
for affID, price := range line.Prices {
lineStr := ""
if line.LineValue != 0 {
lineStr = fmt.Sprintf(" (%g)", line.LineValue)
}
fmt.Printf(" %s%s: %s @ %s\n",
p.Name, lineStr, formatPrice(price.PriceValue), affID)
}
}
}
}
}
}
WebSocket Streaming
Copy
package main
import (
"encoding/json"
"fmt"
"log"
"math"
"math/rand"
"net/url"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
type WSMeta struct {
Type string `json:"type"`
Timestamp string `json:"timestamp"`
}
type WSMessage struct {
EventID string `json:"event_id"`
SportID int `json:"sport_id"`
MarketID int `json:"market_id"`
MarketName string `json:"market_name"`
Participants []Participant `json:"participants"`
Meta WSMeta `json:"meta"`
}
func connectWebSocket() {
u, _ := url.Parse(wsURL)
q := u.Query()
q.Set("key", apiKey)
q.Set("sport_ids", "4")
q.Set("market_ids", "1,2,3")
u.RawQuery = q.Encode()
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
reconnectDelay := 1.0
maxDelay := 30.0
for {
conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
jitter := rand.Float64()
delay := math.Min(reconnectDelay+jitter, maxDelay)
log.Printf("Connection failed: %v. Retrying in %.1fs...", err, delay)
time.Sleep(time.Duration(delay * float64(time.Second)))
reconnectDelay = math.Min(reconnectDelay*2, maxDelay)
continue
}
log.Println("WebSocket connected")
reconnectDelay = 1.0
done := make(chan struct{})
go func() {
defer close(done)
for {
_, message, err := conn.ReadMessage()
if err != nil {
log.Printf("Read error: %v", err)
return
}
var msg WSMessage
if err := json.Unmarshal(message, &msg); err != nil {
continue
}
if msg.Meta.Type == "heartbeat" {
continue
}
fmt.Printf("Update: %s - %s\n", msg.EventID, msg.MarketName)
for _, p := range msg.Participants {
for _, line := range p.Lines {
for affID, price := range line.Prices {
fmt.Printf(" %s: %s @ %s\n",
p.Name, formatPrice(price.PriceValue), affID)
}
}
}
}
}()
select {
case <-done:
log.Println("Connection lost, reconnecting...")
conn.Close()
case <-interrupt:
log.Println("Shutting down...")
conn.WriteMessage(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
)
conn.Close()
return
}
}
}
func main() {
connectWebSocket()
}
Error Handling with Retry
Copy
func apiGetWithRetry(path string, params map[string]string, maxRetries int) ([]byte, error) {
for attempt := 0; attempt < maxRetries; attempt++ {
body, err := apiGet(path, params)
if err == nil {
return body, nil
}
// Check if rate limited
if err.Error()[:12] == "rate limited" {
wait := time.Duration(math.Pow(2, float64(attempt))) * time.Second
jitter := time.Duration(rand.Intn(1000)) * time.Millisecond
log.Printf("Rate limited. Retrying in %v...", wait+jitter)
time.Sleep(wait + jitter)
continue
}
return nil, err
}
return nil, fmt.Errorf("max retries exceeded")
}