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
NewsEvery 30–60 minEvery 10 minEvery 5 min
LineupsEvery 60 minEvery 1 min (15–30 min before lineup lock)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

Lineup lock windows: For NBA, poll every minute in the 15–30 minutes before lineup lock. For NFL, poll every minute immediately after inactives are announced (~90 minutes before kickoff).

Transactions are reported as player news and appear in the standard news and injury feeds — there is no separate transactions endpoint.


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 — far more efficient than fetching the full dataset 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"

Use hours=48 on your first poll of the day. RotoWire editors sometimes update or correct notes from the previous day. A shorter window will miss those edits — 48 hours ensures you capture them.

import requests, time

API_KEY = "YOUR_API_KEY"
POLL_INTERVAL_SECONDS = 300

while True:
    resp = requests.get(
        "https://api.rotowire.com/Basketball/get-nba-injuries.php",
        params={"key": API_KEY, "hours": 0.1}
    )
    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();

Deduplication by Update ID

Every update has a unique Id. Deduplicate before processing to avoid handling the same update twice across polls.

seen_ids = set()

def process_updates(updates):
    for update in updates:
        if update["Id"] in seen_ids:
            continue
        seen_ids.add(update["Id"])
        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);
  }
}

Store seen IDs in a database — not just memory — so you don't reprocess on restart.


Error Handling & 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 in (429,) or resp.status_code >= 500:
                print(f"HTTP {resp.status_code}. Retrying in {delay}s...")
            else:
                resp.raise_for_status()
        except requests.exceptions.RequestException as e:
            print(f"Request failed: {e}. Retrying in {delay}s...")
        time.sleep(delay)
        delay = min(delay * 2, 120)
    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: ${resp.status}`);
    } catch (e) {
      if (i === maxRetries - 1) throw e;
    }
    await new Promise(r => setTimeout(r, delay));
    delay = Math.min(delay * 2, 120000);
  }
}

Avoiding Redundant Calls

  • Only poll sports and leagues relevant to your application
  • Use date to scope requests where supported
  • Respect game schedules — pause or reduce frequency during off-seasons
  • Cache large slow-changing payloads (depth charts, rosters) locally

Summary

  • Use the hours parameter for delta-pulls to avoid fetching the full dataset on every poll
  • Use hours=48 on your first poll of the day to catch any prior-day edits
  • Deduplicate by Id before processing and persist seen IDs across restarts
  • Poll lineups every minute during the lock window — 15–30 min before NBA lineup lock, immediately after NFL inactives are announced
  • Transactions appear in the standard news and injury feeds — there is no separate transactions endpoint
  • Implement exponential backoff on 429 and 5xx responses with a 10s request timeout
  • Cache large slow-changing payloads (depth charts, rosters) locally and refresh infrequently
  • Pause or reduce polling frequency during off-seasons for inactive leagues
  • Never expose your API key in client-side or public code

Did this page help you?