Documentation Index
Fetch the complete documentation index at: https://docs.therundown.io/llms.txt
Use this file to discover all available pages before exploring further.
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:go get github.com/gorilla/websocket
Configuration
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
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 {
retryAfter := resp.Header.Get("Retry-After")
if retryAfter == "" {
retryAfter = "60"
}
return nil, fmt.Errorf("rate limited, retry after %s seconds", retryAfter)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %d %s", resp.StatusCode, resp.Status)
}
return io.ReadAll(resp.Body)
}
Getting Sports
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
type Price struct {
Price float64 `json:"price"`
IsMainLine bool `json:"is_main_line"`
UpdatedAt string `json:"updated_at"`
}
type Line struct {
Value string `json:"value,omitempty"`
Prices map[string]Price `json:"prices"`
}
type Participant struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
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.Value != "" {
lineStr = fmt.Sprintf(" (%s)", line.Value)
}
fmt.Printf(" %s%s: %s @ %s\n",
p.Name, lineStr, formatPrice(price.Price), affID)
}
}
}
}
}
}
WebSocket Streaming
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.Price), 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
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")
}
Next Steps
Getting Live Odds
Detailed guide on fetching odds
WebSocket Streaming
Real-time data streaming guide
Authentication
All authentication methods
Rate Limits
Rate limit details and best practices