Skip to main content
An official JavaScript/TypeScript SDK is coming soon. In the meantime, this guide shows how to use TheRundown API directly with the native Fetch API and WebSocket class — no dependencies required.

Configuration

const API_KEY = process.env.THERUNDOWN_API_KEY || "YOUR_API_KEY";
const BASE_URL = "https://therundown.io/api/v2";
const WS_URL = "wss://therundown.io/api/v2/ws/markets";

Helper Function

async function apiGet(path, params = {}) {
  const url = new URL(`${BASE_URL}${path}`);
  url.searchParams.set("key", API_KEY);
  for (const [key, value] of Object.entries(params)) {
    url.searchParams.set(key, value);
  }

  const response = await fetch(url.toString());

  if (response.status === 429) {
    throw new Error("Rate limited. Check X-RateLimit-Reset header.");
  }

  if (!response.ok) {
    throw new Error(`API error: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

Getting Sports

const { sports } = await apiGet("/sports");

for (const sport of sports) {
  console.log(`${sport.sport_id}: ${sport.sport_name}`);
}

Getting Events with Odds

const today = new Date().toISOString().split("T")[0];

const data = await apiGet(`/sports/4/events/${today}`, {
  market_ids: "1,2,3",       // Moneyline, Spread, Total
  affiliate_ids: "19,23",    // DraftKings, FanDuel
  main_line: "true",
});

function formatPrice(price) {
  if (price === 0.0001) return "N/A";
  return price > 0 ? `+${Math.round(price)}` : String(Math.round(price));
}

for (const event of data.events) {
  const away = event.teams[0].name;
  const home = event.teams[1].name;
  console.log(`\n${away} @ ${home}`);

  for (const market of event.markets || []) {
    console.log(`  ${market.name}:`);
    for (const participant of market.participants) {
      for (const line of participant.lines) {
        const lineStr = line.line != null ? ` (${line.line})` : "";
        const prices = Object.entries(line.prices)
          .map(([id, p]) => `${formatPrice(p.price)} @${id}`)
          .join("  ");
        console.log(`    ${participant.name}${lineStr}: ${prices}`);
      }
    }
  }
}

Filtering by Affiliate

// Only FanDuel lines
const data = await apiGet(`/sports/4/events/${today}`, {
  market_ids: "1,2,3",
  affiliate_ids: "23",  // FanDuel only
  main_line: "true",
});

Getting Player Props

const props = await apiGet(`/sports/4/events/${today}`, {
  market_ids: "29,35,38,39",  // Points, Rebounds, 3PT, Assists
  affiliate_ids: "19",
});

for (const event of props.events) {
  console.log(`\n${event.teams[0].name} @ ${event.teams[1].name}`);
  for (const market of event.markets || []) {
    console.log(`  ${market.name}:`);
    for (const participant of market.participants) {
      for (const line of participant.lines) {
        const price = line.prices["19"]?.price ?? "N/A";
        console.log(`    ${participant.name}: ${line.line} (${price})`);
      }
    }
  }
}

Historical Odds

const eventId = "abc123";

// Full market history
const { history } = await apiGet(`/events/${eventId}/markets/history`, {
  affiliate_ids: "19",
});

for (const entry of history) {
  console.log(
    `${entry.timestamp}: ${entry.market_name} ${entry.line} @ ${entry.price}`
  );
}

// Opening lines
const openers = await apiGet(`/events/${eventId}/openers`, {
  market_ids: "1,2,3",
});

// Closing lines
const closing = await apiGet(`/events/${eventId}/closing`, {
  market_ids: "1,2,3",
});

WebSocket Streaming

function connectWebSocket(options = {}) {
  const { sportIds = "4", marketIds = "1,2,3", onUpdate } = options;

  const url = `${WS_URL}?key=${API_KEY}&sport_ids=${sportIds}&market_ids=${marketIds}`;
  const ws = new WebSocket(url);

  ws.onopen = () => {
    console.log("WebSocket connected");
  };

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);

    // Skip heartbeats
    if (data.meta?.type === "heartbeat") return;

    if (onUpdate) {
      onUpdate(data);
    } else {
      console.log(`Update: ${data.event_id} - ${data.market_name}`);
    }
  };

  ws.onerror = (error) => {
    console.error("WebSocket error:", error);
  };

  ws.onclose = (event) => {
    console.log(`WebSocket closed: ${event.code}`);
  };

  return ws;
}

// Usage
const ws = connectWebSocket({
  sportIds: "4",
  marketIds: "1,2,3",
  onUpdate: (data) => {
    console.log(`${data.event_id}: ${data.market_name}`);
    for (const p of data.participants || []) {
      for (const line of p.lines || []) {
        for (const [affId, price] of Object.entries(line.prices || {})) {
          console.log(`  ${p.name}: ${price.price} (${affId})`);
        }
      }
    }
  },
});

WebSocket with Auto-Reconnect

function createReconnectingSocket(options = {}) {
  const {
    sportIds = "4",
    marketIds = "1,2,3",
    onUpdate,
    onConnect,
    maxDelay = 30000,
  } = options;

  let ws;
  let reconnectDelay = 1000;
  let closed = false;

  function connect() {
    if (closed) return;

    const url = `${WS_URL}?key=${API_KEY}&sport_ids=${sportIds}&market_ids=${marketIds}`;
    ws = new WebSocket(url);

    ws.onopen = () => {
      console.log("WebSocket connected");
      reconnectDelay = 1000;
      if (onConnect) onConnect();
    };

    ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.meta?.type === "heartbeat") return;
      if (onUpdate) onUpdate(data);
    };

    ws.onclose = () => {
      if (closed) return;
      const jitter = Math.random() * 1000;
      const delay = Math.min(reconnectDelay + jitter, maxDelay);
      console.log(`Reconnecting in ${Math.round(delay)}ms...`);
      setTimeout(() => {
        reconnectDelay = Math.min(reconnectDelay * 2, maxDelay);
        connect();
      }, delay);
    };

    ws.onerror = () => {
      ws.close();
    };
  }

  connect();

  return {
    close() {
      closed = true;
      ws?.close();
    },
  };
}

// Usage
const connection = createReconnectingSocket({
  sportIds: "4",
  marketIds: "1,2,3",
  onUpdate: (data) => {
    console.log(`Update: ${data.event_id} ${data.market_name}`);
  },
  onConnect: () => {
    console.log("Ready to receive updates");
  },
});

// Later: connection.close();

Error Handling with Retry

async function apiGetWithRetry(path, params = {}, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const url = new URL(`${BASE_URL}${path}`);
    url.searchParams.set("key", API_KEY);
    for (const [key, value] of Object.entries(params)) {
      url.searchParams.set(key, value);
    }

    const response = await fetch(url.toString());

    if (response.ok) {
      return response.json();
    }

    if (response.status === 429) {
      const wait = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
      console.log(`Rate limited. Retrying in ${Math.round(wait)}ms...`);
      await new Promise((resolve) => setTimeout(resolve, wait));
      continue;
    }

    throw new Error(`API error: ${response.status} ${response.statusText}`);
  }

  throw new Error("Max retries exceeded");
}

TypeScript Types

If you are using TypeScript, here are type definitions for the core response objects:
interface Sport {
  sport_id: number;
  sport_name: string;
}

interface Team {
  team_id: number;
  name: string;
}

interface Price {
  price: number;
  affiliate_id: number;
  timestamp?: string;
}

interface Line {
  line: number | null;
  is_main: boolean;
  prices: Record<string, Price>;
}

interface Participant {
  participant_id: number;
  name: string;
  participant_type?: string;
  lines: Line[];
}

interface Market {
  market_id: number;
  name: string;
  period_id: number;
  participants: Participant[];
}

interface Event {
  event_id: string;
  sport_id: number;
  teams: Team[];
  markets: Market[];
}

interface EventsResponse {
  events: Event[];
}

Next Steps