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
Copy
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
Copy
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
Copy
const { sports } = await apiGet("/sports");
for (const sport of sports) {
console.log(`${sport.sport_id}: ${sport.sport_name}`);
}
Getting Events with Odds
Copy
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
Copy
// 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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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:Copy
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[];
}