Polling Best Practices

Polling Best Practices

The RotoWire API is a REST polling API — there are no webhooks or push events. This guide explains how to poll efficiently, avoid redundant work, and handle errors reliably.


Poll Intervals by Data Type

Different data types update at different frequencies. Polling too aggressively wastes quota and slows your app; polling too infrequently means stale data.

Data TypeOff-DayGame Day (Pre-Game)Game Day (In-Game / Post-Game)
InjuriesEvery 30–60 minEvery 10–15 minEvery 5 min
News & HeadlinesEvery 30–60 minEvery 10 minEvery 5 min
LineupsEvery 60 minEvery 5 min (2 hrs before tip)Every 10 min
Depth ChartsEvery 4–6 hrsEvery 60 minEvery 60 min
DFS ProjectionsOnce dailyOnce after lineup lockNot applicable
Players / RostersEvery 4–6 hrsEvery 4–6 hrsEvery 4–6 hrs
TransactionsEvery 30–60 minEvery 30 minEvery 30 min

Rule of thumb: If data is time-sensitive for lineups or DFS, tighten your pre-game window. If it's roster or depth chart data, daily is usually enough outside of trade deadlines.


Delta-Pull with the hours Parameter

Most news and injury endpoints support an hours parameter. Use it to fetch only updates published within the last N hours — this is dramatically more efficient than fetching the full dataset on every poll.

# Fetch only updates from the last 30 minutes
curl "https://api.rotowire.com/Basketball/get-nba-news-updates.php?key=YOUR_API_KEY&hours=0.5"

# Fetch updates from the last 2 hours
curl "https://api.rotowire.com/Basketball/get-nba-injuries.php?key=YOUR_API_KEY&hours=2"
import requests, time

API_KEY = "YOUR_API_KEY"
POLL_INTERVAL_SECONDS = 300  # 5 minutes

while True:
    resp = requests.get(
        "https://api.rotowire.com/Basketball/get-nba-injuries.php",
        params={"key": API_KEY, "hours": 0.1}  # last 6 minutes
    )
    updates = resp.json().get("Updates", [])
    for u in updates:
        process_update(u)
    time.sleep(POLL_INTERVAL_SECONDS)
const API_KEY = 'YOUR_API_KEY';
const POLL_MS = 5 * 60 * 1000;

async function poll() {
  const resp = await fetch(
    `https://api.rotowire.com/Basketball/get-nba-injuries.php?key=${API_KEY}&hours=0.1`
  );
  const { Updates = [] } = await resp.json();
  for (const update of Updates) {
    processUpdate(update);
  }
}

setInterval(poll, POLL_MS);
poll(); // run immediately on start

Deduplication by Update ID

Every update object has a unique Id field. Even when using the hours parameter, the same update can appear across multiple polls if it falls within the time window. Always deduplicate by Id before processing.

seen_ids = set()

def process_updates(updates):
    for update in updates:
        uid = update["Id"]
        if uid in seen_ids:
            continue
        seen_ids.add(uid)
        handle_update(update)
const seenIds = new Set();

function processUpdates(updates) {
  for (const update of updates) {
    if (seenIds.has(update.Id)) continue;
    seenIds.add(update.Id);
    handleUpdate(update);
  }
}

Persistence: Store seen IDs in a database (not just in memory) if your process restarts, so you don't reprocess on startup.


Error Handling & Backoff

Network errors and transient 5xx responses are normal for any HTTP API. Never retry immediately — use exponential backoff.

import requests, time

def fetch_with_retry(url, params, max_retries=5):
    delay = 5
    for attempt in range(max_retries):
        try:
            resp = requests.get(url, params=params, timeout=10)
            if resp.status_code == 200:
                return resp.json()
            elif resp.status_code == 429:
                print(f"Rate limited. Waiting {delay}s...")
            elif resp.status_code >= 500:
                print(f"Server error {resp.status_code}. Retrying in {delay}s...")
            else:
                resp.raise_for_status()  # 4xx errors are not retryable
        except requests.exceptions.RequestException as e:
            print(f"Request failed: {e}. Retrying in {delay}s...")
        time.sleep(delay)
        delay = min(delay * 2, 120)  # cap at 2 minutes
    raise Exception("Max retries exceeded")
async function fetchWithRetry(url, maxRetries = 5) {
  let delay = 5000;
  for (let i = 0; i < maxRetries; i++) {
    try {
      const resp = await fetch(url);
      if (resp.ok) return await resp.json();
      if (resp.status === 429 || resp.status >= 500) {
        console.warn(`HTTP ${resp.status} — retrying in ${delay}ms`);
      } else {
        throw new Error(`Non-retryable error: ${resp.status}`);
      }
    } catch (e) {
      if (i === maxRetries - 1) throw e;
      console.warn(`Fetch error: ${e.message} — retrying in ${delay}ms`);
    }
    await new Promise(r => setTimeout(r, delay));
    delay = Math.min(delay * 2, 120000);
  }
}

Avoiding Redundant Calls

Don't poll all sports at once. Only poll the sports and leagues relevant to your application.

Use date to scope requests on endpoints that support it:

# Only today's data
curl "https://api.rotowire.com/Baseball/get-mlb-injuries.php?key=YOUR_API_KEY&date=2025-04-10"

Respect game schedules. There is no NBA data to fetch in July. Build schedule-awareness into your polling loop to pause or reduce frequency during off-seasons.

Cache full-dataset responses. Endpoints like get-nba-players.php or get-nba-depth-charts.php return large payloads that rarely change. Store them locally and refresh infrequently.

import time

_depth_chart_cache = {"data": None, "expires": 0}

def get_depth_charts():
    if time.time() < _depth_chart_cache["expires"]:
        return _depth_chart_cache["data"]
    resp = requests.get("https://api.rotowire.com/Basketball/get-nba-depth-charts.php",
                        params={"key": API_KEY})
    _depth_chart_cache["data"] = resp.json()
    _depth_chart_cache["expires"] = time.time() + 3600  # cache for 1 hour
    return _depth_chart_cache["data"]

Checklist

  • Use hours param for news/injury delta-pulls
  • Deduplicate by Id field before processing
  • Persist seen IDs across process restarts
  • Implement exponential backoff on 429 and 5xx
  • Set a request timeout (10s recommended)
  • Cache slow-changing data (depth charts, rosters)
  • Pause polling during off-seasons for inactive leagues
  • Never embed your API key in client-side or public code